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: JEntityFactory.java 9860 2006-11-23 16:40:22Z durieuxp $
0023: * --------------------------------------------------------------------------
0024: */package org.objectweb.jonas_ejb.container;
0025:
0026: import java.io.Serializable;
0027: import java.util.ArrayList;
0028: import java.util.Collection;
0029: import java.util.Date;
0030: import java.util.HashMap;
0031: import java.util.Iterator;
0032: import java.util.LinkedList;
0033: import java.util.List;
0034: import java.util.ListIterator;
0035:
0036: import javax.ejb.EJBException;
0037: import javax.ejb.EntityBean;
0038: import javax.ejb.FinderException;
0039: import javax.ejb.Timer;
0040: import javax.ejb.TimerService;
0041: import javax.naming.Context;
0042: import javax.naming.NamingException;
0043: import javax.transaction.RollbackException;
0044: import javax.transaction.SystemException;
0045: import javax.transaction.Transaction;
0046:
0047: import org.objectweb.jonas_ejb.deployment.api.EntityDesc;
0048: import org.objectweb.jonas_ejb.deployment.api.EntityJdbcCmp1Desc;
0049: import org.objectweb.jonas_ejb.deployment.api.EntityJdbcCmp2Desc;
0050:
0051: import org.objectweb.util.monolog.api.BasicLevel;
0052:
0053: /**
0054: * This class is a factory for an Entity Bean. It is responsible for
0055: * - managing Home and LocalHome.
0056: * - managing a pool of instances/contexts
0057: * - keeping the list of PKs and the associated JEntitySwitch's
0058: * - keeping the JNDI context for this component (java:comp/env)
0059: * @author Philippe Coq, Philippe Durieux
0060: */
0061: /**
0062: * @author coqp
0063: *
0064: * TODO To change the template for this generated type comment go to
0065: * Window - Preferences - Java - Code Style - Code Templates
0066: */
0067: public class JEntityFactory extends JFactory implements TimerService {
0068:
0069: /**
0070: * optional home
0071: */
0072: protected JEntityHome home = null;
0073:
0074: /**
0075: * @return the home if it exists
0076: */
0077: public JHome getHome() {
0078: return home;
0079: }
0080:
0081: /**
0082: * optional local home
0083: */
0084: protected JEntityLocalHome localhome = null;
0085:
0086: /**
0087: * @return the local home if it exists
0088: */
0089: public JLocalHome getLocalHome() {
0090: return localhome;
0091: }
0092:
0093: /**
0094: * lock policy
0095: */
0096: protected int lockPolicy;
0097:
0098: /**
0099: * @return lockPolicy
0100: */
0101: public int getLockPolicy() {
0102: return lockPolicy;
0103: }
0104:
0105: /**
0106: * enable the prefetch for CMP2 bean
0107: */
0108: protected boolean prefetch = false;
0109:
0110: /**
0111: * @return true if prefetch enable
0112: */
0113: public boolean isPrefetch() {
0114: return prefetch;
0115: }
0116:
0117: /**
0118: * True if CMP2 container
0119: */
0120: protected boolean cmp2 = true;
0121:
0122: /**
0123: * @return true if CMP2 container
0124: */
0125: public boolean isCMP2() {
0126: return cmp2;
0127: }
0128:
0129: /**
0130: * shared if the EJB container is not the only one to modify
0131: * the bean state on the database, or if a cluster of container
0132: * access the bean concurrently.
0133: */
0134: protected boolean shared = false;
0135:
0136: /**
0137: * @return true if shared
0138: */
0139: public boolean isShared() {
0140: return shared;
0141: }
0142:
0143: /**
0144: * reentrant if instance can be used concurrently
0145: */
0146: protected boolean reentrant;
0147:
0148: /**
0149: * @return true if reentrant
0150: */
0151: public boolean isReentrant() {
0152: return reentrant;
0153: }
0154:
0155: /**
0156: * @return true if the bean is shared
0157: */
0158: public boolean getSelectForUpdate() {
0159: return shared
0160: && lockPolicy == EntityDesc.LOCK_CONTAINER_SERIALIZED_TRANSACTED;
0161: // In case of database lock-policy, setting the selectForUpdate option
0162: // leads sometimes to deadlocks, at least on postgresql.
0163: //return shared;
0164: }
0165:
0166: /**
0167: * Number of seconds before reading again instances for read-only.
0168: */
0169: protected int readTimeout; // sec.
0170:
0171: /**
0172: * @return read timeout in sec.
0173: */
0174: public int getReadTimeout() {
0175: return readTimeout;
0176: }
0177:
0178: /**
0179: * Number of seconds before releasing objects when not used.
0180: */
0181: private int inactivityTimeout; // sec.
0182:
0183: /**
0184: * @return inactivity timeout in sec.
0185: */
0186: public int getInactivityTimeout() {
0187: return inactivityTimeout;
0188: }
0189:
0190: /**
0191: * Set the inactivity timeout (jonas admin)
0192: * Not used today
0193: */
0194: public void setInactivityTimeout(int i) {
0195: inactivityTimeout = i;
0196: }
0197:
0198: /**
0199: * Number of seconds before loocking for deadlock when stuck
0200: */
0201: private int deadlockTimeout; // sec.
0202:
0203: /**
0204: * @return deadlock timeout in sec.
0205: */
0206: public int getDeadlockTimeout() {
0207: return deadlockTimeout;
0208: }
0209:
0210: /**
0211: * Set the deadlock timeout (jonas admin)
0212: * Not used today
0213: */
0214: public void setDeadlockTimeout(int i) {
0215: deadlockTimeout = i;
0216: }
0217:
0218: /**
0219: * Number of seconds before storing objects (CS policy only)
0220: */
0221: private int passivationTimeout; // sec.
0222:
0223: /**
0224: * @return passivation timeout in sec.
0225: */
0226: public int getPassivationTimeout() {
0227: return passivationTimeout;
0228: }
0229:
0230: /**
0231: * Set the passivation timeout (jonas admin)
0232: * Not used today
0233: */
0234: public void setPassivationTimeout(int i) {
0235: TraceEjb.logger.log(BasicLevel.WARN, ejbname);
0236: passivationTimeout = i;
0237: }
0238:
0239: /**
0240: * Datasource in case of CMP
0241: */
0242: protected Object datasource = null;
0243:
0244: /**
0245: * datasource name
0246: */
0247: String dsname = null;
0248:
0249: /**
0250: * @return the Datasource used for CMP
0251: */
0252: public Object getDataSource() {
0253: if (datasource == null) {
0254: TraceEjb.logger.log(BasicLevel.ERROR, ejbname
0255: + ": datasource not defined");
0256: }
0257: return datasource;
0258: }
0259:
0260: /**
0261: * Dummy method that defines the FinderException in the throws clause
0262: * to can catch this exception in any case in the JentityHome.vm
0263: * @param dummy if true do nothing, else throw the FinderException
0264: * @throws FinderException if dummy is false
0265: */
0266: public void dummyFinderException(boolean dummy)
0267: throws FinderException {
0268: if (!dummy) {
0269: throw new FinderException("dummy exception !!!");
0270: }
0271: }
0272:
0273: // -----------------------------------------------------------------
0274: // Start - Stop
0275: // -----------------------------------------------------------------
0276:
0277: /**
0278: * constructor must be without parameters (required by Jorm)
0279: */
0280: public JEntityFactory() {
0281: TraceEjb.interp.log(BasicLevel.DEBUG, "");
0282: }
0283:
0284: /**
0285: * Init this object
0286: * @param dd the deployment descriptor
0287: * @param cont the Container
0288: */
0289: public void init(EntityDesc dd, JContainer cont) {
0290: super .init(dd, cont);
0291: reentrant = dd.isReentrant();
0292: shared = dd.isShared();
0293: lockPolicy = dd.getLockPolicy();
0294: prefetch = dd.isPrefetch();
0295: hardLimit = dd.isHardLimit();
0296:
0297: // Check lock-policy compatible with shared and prefetch flags
0298: switch (lockPolicy) {
0299: case EntityDesc.LOCK_CONTAINER_READ_UNCOMMITTED:
0300: if (prefetch) {
0301: prefetch = false;
0302: TraceEjb.logger
0303: .log(BasicLevel.WARN,
0304: "prefetch flag forced to false, with CRU lock policy");
0305: }
0306: TraceEjb.logger
0307: .log(BasicLevel.WARN,
0308: "This policy (CRU) is deprecated. Use CRW instead!");
0309: break;
0310: case EntityDesc.LOCK_CONTAINER_READ_WRITE:
0311: if (prefetch) {
0312: prefetch = false;
0313: TraceEjb.logger
0314: .log(BasicLevel.WARN,
0315: "prefetch flag forced to false with CRW lock policy");
0316: }
0317: if (shared) {
0318: shared = false;
0319: TraceEjb.logger
0320: .log(BasicLevel.WARN,
0321: "shared flag forced to false with CRW lock policy");
0322: }
0323: break;
0324: case EntityDesc.LOCK_CONTAINER_SERIALIZED:
0325: if (shared) {
0326: TraceEjb.logger
0327: .log(BasicLevel.WARN,
0328: "shared flag should be set to false with CS policy");
0329: TraceEjb.logger.log(BasicLevel.WARN,
0330: "Consider switching to the new CST policy");
0331: }
0332: break;
0333: case EntityDesc.LOCK_DATABASE:
0334: if (!shared) {
0335: shared = true;
0336: TraceEjb.logger.log(BasicLevel.WARN,
0337: "shared flag forced to true with DB policy");
0338: }
0339: break;
0340: }
0341: inactivityTimeout = dd.getInactivityTimeout();
0342: passivationTimeout = dd.getPassivationTimeout();
0343: deadlockTimeout = dd.getDeadlockTimeout();
0344: readTimeout = dd.getReadTimeout();
0345: setMaxWaitTime(dd.getMaxWaitTime());
0346:
0347: // Finds the DataSource associated to the bean
0348: // Useful for Entity with Container Managed Persistence.
0349: if (dd instanceof EntityJdbcCmp1Desc) {
0350: cmp2 = false;
0351: dsname = ((EntityJdbcCmp1Desc) dd).getDatasourceJndiName();
0352: if (dsname != null) {
0353: try {
0354: datasource = getInitialContext().lookup(dsname);
0355: } catch (NamingException e) {
0356: String err = dsname
0357: + " is not known by the EJB server.";
0358: TraceEjb.logger.log(BasicLevel.ERROR, err, e);
0359: throw new EJBException(err, e);
0360: }
0361: }
0362: if (lockPolicy == EntityDesc.LOCK_CONTAINER_READ_WRITE) {
0363: throw new EJBException(
0364: "Cannot use CONTAINER_READ_WRITE with a CMP1 bean");
0365: }
0366: }
0367:
0368: if (dd instanceof EntityJdbcCmp2Desc) {
0369: cmp2 = true;
0370: dsname = ((EntityJdbcCmp2Desc) dd).getDatasourceJndiName();
0371: if (dsname != null) {
0372: try {
0373: datasource = getInitialContext().lookup(dsname);
0374: } catch (NamingException e) {
0375: String err = dsname
0376: + " is not known by the EJB server.";
0377: TraceEjb.logger.log(BasicLevel.ERROR, err);
0378: throw new EJBException(err, e);
0379: }
0380: }
0381: }
0382:
0383: // Create the Home if defined in DD
0384: Class homeclass = null;
0385: String clname = dd.getFullWrpHomeName();
0386: if (clname != null) {
0387: try {
0388: homeclass = cont.getClassLoader().loadClass(clname);
0389: } catch (ClassNotFoundException e) {
0390: throw new EJBException(ejbname + "Cannot load "
0391: + clname, e);
0392: }
0393: if (TraceEjb.isDebugIc()) {
0394: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname + ": "
0395: + clname + " loaded");
0396: }
0397: try {
0398: // new JEntityHome(dd, this)
0399: int nbp = 2;
0400: Class[] ptype = new Class[nbp];
0401: Object[] pobj = new Object[nbp];
0402: ptype[0] = org.objectweb.jonas_ejb.deployment.api.EntityDesc.class;
0403: pobj[0] = (Object) dd;
0404: ptype[1] = org.objectweb.jonas_ejb.container.JEntityFactory.class;
0405: pobj[1] = (Object) this ;
0406: home = (JEntityHome) homeclass.getConstructor(ptype)
0407: .newInstance(pobj);
0408: } catch (Exception e) {
0409: throw new EJBException(
0410: ejbname + " Cannot create home ", e);
0411: }
0412:
0413: // register it in JNDI
0414: try {
0415: home.register();
0416: } catch (Exception e) {
0417: throw new EJBException(ejbname
0418: + " Cannot register home ", e);
0419: }
0420: }
0421:
0422: // Create the LocalHome if defined in DD
0423: clname = dd.getFullWrpLocalHomeName();
0424: if (clname != null) {
0425: try {
0426: homeclass = cont.getClassLoader().loadClass(clname);
0427: } catch (ClassNotFoundException e) {
0428: TraceEjb.logger.log(BasicLevel.ERROR, ejbname
0429: + " cannot load " + clname);
0430: throw new EJBException("Cannot load " + clname, e);
0431: }
0432: if (TraceEjb.isDebugIc()) {
0433: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname + ": "
0434: + clname + " loaded");
0435: }
0436: try {
0437: // new JEntityLocalHome(dd, this)
0438: int nbp = 2;
0439: Class[] ptype = new Class[nbp];
0440: Object[] pobj = new Object[nbp];
0441: ptype[0] = org.objectweb.jonas_ejb.deployment.api.EntityDesc.class;
0442: pobj[0] = (Object) dd;
0443: ptype[1] = org.objectweb.jonas_ejb.container.JEntityFactory.class;
0444: pobj[1] = (Object) this ;
0445: localhome = (JEntityLocalHome) homeclass
0446: .getConstructor(ptype).newInstance(pobj);
0447: } catch (Exception e) {
0448: throw new EJBException(ejbname
0449: + " Cannot create localhome ", e);
0450: }
0451: // register it in JNDI
0452: try {
0453: localhome.register();
0454: } catch (Exception e) {
0455: throw new EJBException(ejbname
0456: + " Cannot register localhome ", e);
0457: }
0458: }
0459: }
0460:
0461: /**
0462: * stop this EJB.
0463: * Mainly unregister it in JNDI.
0464: */
0465: public void stop() {
0466: if (TraceEjb.isDebugIc()) {
0467: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname);
0468: }
0469: try {
0470: if (home != null) {
0471: home.unregister();
0472: }
0473: if (localhome != null) {
0474: localhome.unregister();
0475: }
0476: } catch (NamingException e) {
0477: }
0478: stopContainer();
0479: }
0480:
0481: // --------------------------------------------------------------
0482: // Instance Pool Management
0483: // --------------------------------------------------------------
0484:
0485: /**
0486: * freelist of JEntityContext's
0487: */
0488: protected List bctxlist = new ArrayList();
0489:
0490: /**
0491: * @return the Instance pool size for this Ejb
0492: */
0493: public int getPoolSize() {
0494: return bctxlist.size();
0495: }
0496:
0497: /**
0498: * Current number of instances in memory
0499: */
0500: protected int instanceCount = 0;
0501:
0502: /**
0503: * @return current cache size
0504: */
0505: public int getCacheSize() {
0506: return instanceCount;
0507: }
0508:
0509: /**
0510: * @return true if max-cache-size has been reached
0511: */
0512: public boolean tooManyInstances() {
0513: return (maxCacheSize > 0 && instanceCount >= maxCacheSize);
0514: }
0515:
0516: /**
0517: * True if cannot overtake max-cache-size
0518: */
0519: protected boolean hardLimit = false;
0520:
0521: /**
0522: * @return true if hard limit for max-cache-size
0523: */
0524: public boolean isHardLimit() {
0525: return hardLimit;
0526: }
0527:
0528: /**
0529: * nb of threads waiting for an instance
0530: */
0531: private int currentWaiters = 0;
0532:
0533: /**
0534: * @return current number of instance waiters
0535: */
0536: public int getCurrentWaiters() {
0537: return currentWaiters;
0538: }
0539:
0540: /**
0541: * max nb of milliseconds to wait for an instance when pool is empty
0542: */
0543: private long waiterTimeout = 10000L;
0544:
0545: /**
0546: * @return waiter timeout in seconds
0547: */
0548: public int getMaxWaitTime() {
0549: return (int) (waiterTimeout / 1000L);
0550: }
0551:
0552: /**
0553: * @param sec max time to wait for a connection, in seconds
0554: */
0555: public void setMaxWaitTime(int sec) {
0556: waiterTimeout = sec * 1000L;
0557: }
0558:
0559: /**
0560: * Init the pool of instances
0561: */
0562: public void initInstancePool() {
0563: // pre-allocate a set of JEntityContext (bean instances)
0564: Context bnctx = setComponentContext(); // for createNewInstance
0565: for (int i = 0; i < minPoolSize; i++) {
0566: JEntityContext ctx = null;
0567: try {
0568: ctx = createNewInstance(null);
0569: synchronized (bctxlist) {
0570: bctxlist.add(ctx);
0571: instanceCount++;
0572: }
0573: } catch (Exception e) {
0574: TraceEjb.logger.log(BasicLevel.WARN, ejbname
0575: + " cannot create new instance", e);
0576: break;
0577: }
0578: }
0579: resetComponentContext(bnctx);
0580: }
0581:
0582: /**
0583: * Get a Context from the pool, or create a new one if no more
0584: * available in the pool.
0585: * This JContext must be initialized then by the caller.
0586: * @return a JEntityContext, not initialized.
0587: */
0588: public JEntityContext getJContext(JEntitySwitch es) {
0589: if (TraceEjb.isDebugIc()) {
0590: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname);
0591: }
0592: JEntityContext bctx = null;
0593:
0594: // Prevent a max-cache-size reached exception, if possible.
0595: int limit = maxCacheSize - maxCacheSize / 15;
0596: if (instanceCount >= limit && limit > minPoolSize) {
0597: // ask swapper to call reduceCache()
0598: // if maxcachesize will be reached soon, or is reached, or is highly
0599: // overtaken.
0600: if (instanceCount > maxCacheSize + limit
0601: || instanceCount == limit
0602: || instanceCount == maxCacheSize) {
0603: if (TraceEjb.isDebugSwapper()) {
0604: TraceEjb.swapper.log(BasicLevel.DEBUG, ejbname
0605: + ": Too many instances :" + instanceCount
0606: + ", max=" + maxCacheSize);
0607: }
0608: cont.registerBF(this );
0609: }
0610: }
0611:
0612: // try to find a free context in the pool
0613: long timetowait = waiterTimeout;
0614: long starttime = 0;
0615: synchronized (bctxlist) {
0616: while (bctx == null) {
0617: if (bctxlist.isEmpty()) {
0618: // pool is empty
0619: if (!hardLimit || !tooManyInstances()) {
0620: // If can create more instances, go ahead.
0621: instanceCount++;
0622: break;
0623: }
0624: // If a timeout has been specified, wait
0625: if (timetowait > 0) {
0626: currentWaiters++;
0627: if (starttime == 0) {
0628: starttime = System.currentTimeMillis();
0629: if (TraceEjb.isDebugSwapper()) {
0630: TraceEjb.swapper.log(BasicLevel.DEBUG,
0631: "WAITING " + ejbname);
0632: }
0633: }
0634: try {
0635: bctxlist.wait(timetowait);
0636: } catch (InterruptedException e) {
0637: TraceEjb.swapper.log(BasicLevel.WARN,
0638: "INTERRUPTED " + ejbname);
0639: } finally {
0640: currentWaiters--;
0641: }
0642: long stoptime = System.currentTimeMillis();
0643: long stillwaited = stoptime - starttime;
0644: timetowait = waiterTimeout - stillwaited;
0645: } else {
0646: // max-wait-time elapsed
0647: TraceEjb.swapper.log(BasicLevel.ERROR,
0648: "max-cache-size reached on " + ejbname);
0649: throw new EJBException(
0650: "max-cache-size reached on " + ejbname);
0651: }
0652: } else {
0653: // we got an instance from the pool.
0654: bctx = (JEntityContext) bctxlist.remove(0);
0655: }
0656: }
0657: }
0658:
0659: if (bctx == null) {
0660: // create a new one if pool empty
0661: try {
0662: bctx = createNewInstance(es);
0663: } catch (Exception e) {
0664: instanceCount--;
0665: throw new EJBException("Cannot create a new instance ",
0666: e);
0667: }
0668: }
0669:
0670: // DEBUG
0671: if (bctx.isInitialized()) {
0672: TraceEjb.context.log(BasicLevel.ERROR, ejbname
0673: + "Context already initialized!");
0674: }
0675:
0676: return bctx;
0677: }
0678:
0679: /**
0680: * Release a Context
0681: * @param ctx - The JContext to release.
0682: * @param poolaction 0=never pool, 1=pool if < MinPoolSize, 2=pool if < MaxCacheSize
0683: */
0684: public void releaseJContext(JContext ctx, int poolaction) {
0685: TraceEjb.context.log(BasicLevel.DEBUG, ejbname);
0686: JEntityContext bctx = (JEntityContext) ctx;
0687: bctx.razEntityContext();
0688: boolean poolit = true;
0689: synchronized (bctxlist) {
0690: switch (poolaction) {
0691: case 0:
0692: poolit = false;
0693: break;
0694: case 1:
0695: poolit = (bctxlist.size() < minPoolSize);
0696: break;
0697: default:
0698: poolit = !tooManyInstances();
0699: break;
0700: }
0701: if (poolit) {
0702: bctxlist.add(bctx);
0703: } else {
0704: instanceCount--;
0705: }
0706: // Someone may be waiting for an instance.
0707: if (currentWaiters > 0) {
0708: bctxlist.notify();
0709: }
0710: }
0711: }
0712:
0713: /**
0714: * Release a Context [deprecated]
0715: * @param ctx - The JContext to release.
0716: */
0717: public void releaseJContext(JContext ctx) {
0718: releaseJContext(ctx, 2);
0719: }
0720:
0721: protected JEntityContext createNewContext(EntityBean bean) {
0722: return new JEntityContext(this , bean);
0723: }
0724:
0725: /**
0726: * Create a new instance of the bean and its EntityContext
0727: * In case of CMP, the bean class is derived to manage entity persistence.
0728: * @return JEntityContext
0729: * @throws Exception cannot instantiate bean
0730: */
0731: protected JEntityContext createNewInstance(JEntitySwitch es)
0732: throws Exception {
0733: if (TraceEjb.isDebugIc()) {
0734: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname);
0735: }
0736:
0737: // create the bean instance
0738: EntityBean bean = null;
0739: try {
0740: bean = (EntityBean) beanclass.newInstance();
0741: } catch (Exception e) {
0742: TraceEjb.logger.log(BasicLevel.ERROR, ejbname
0743: + " cannot instantiate bean " + instanceCount);
0744: throw e;
0745: }
0746:
0747: // create a new EntityContext and bind it to the instance
0748: JEntityContext ec = createNewContext(bean);
0749: bean.setEntityContext(ec);
0750: ec.setState(1);
0751:
0752: return ec;
0753: }
0754:
0755: /**
0756: * Synchronize all dirty instances
0757: * Does nothing if not CS policy.
0758: * @param alwaysStore True if we want to store modify instances
0759: * even if passivation timeout is not reached.
0760: */
0761: public void syncDirty(boolean alwaysStore) {
0762:
0763: if (!mustSyncDirtyEntities) {
0764: // nothing to do
0765: return;
0766: }
0767:
0768: if (TraceEjb.isDebugSwapper()) {
0769: TraceEjb.swapper.log(BasicLevel.DEBUG, ejbname
0770: + " cacheSize = " + getCacheSize());
0771: TraceEjb.swapper.log(BasicLevel.DEBUG, ejbname
0772: + " dirtyList size = " + dirtyList.size());
0773: }
0774:
0775: // Set the JNDI component context (for ejbStore)
0776: Context bnctx = setComponentContext();
0777:
0778: // try to passivate all instances and build a new list.
0779: LinkedList wsdone = new LinkedList();
0780: synchronized (this ) {
0781: for (ListIterator i = dirtyList.listIterator(0); i
0782: .hasNext();) {
0783: JEntitySwitch es = (JEntitySwitch) i.next();
0784: switch (es.passivateIH(alwaysStore, false)) {
0785: case JEntitySwitch.ALL_DONE:
0786: i.remove();
0787: break;
0788: case JEntitySwitch.STORED:
0789: i.remove();
0790: wsdone.addLast(es);
0791: break;
0792: case JEntitySwitch.NOT_DONE:
0793: if (TraceEjb.isDebugSwapper()) {
0794: TraceEjb.swapper.log(BasicLevel.DEBUG, ejbname
0795: + " busy");
0796: }
0797: break;
0798: }
0799: }
0800: }
0801:
0802: // for each instance passivated, notify it's OK to continue.
0803: // must be done after all instances are passivated, in case of relations.
0804: mustSyncDirtyEntities = false; // do this before endIH
0805: for (ListIterator i = wsdone.listIterator(0); i.hasNext();) {
0806: JEntitySwitch es = (JEntitySwitch) i.next();
0807: es.endIH();
0808: }
0809:
0810: // Reset old value for component context
0811: resetComponentContext(bnctx);
0812: }
0813:
0814: /**
0815: * Reduce number of instances in memory
0816: * passivate all instances that are not busy and not used very recently
0817: */
0818: public void reduceCache() {
0819: TraceEjb.swapper.log(BasicLevel.DEBUG, ejbname);
0820:
0821: // Get the list of PK.
0822: HashMap clone = null;
0823: synchronized (this ) {
0824: clone = (HashMap) pklist.clone();
0825: }
0826: if (clone.size() == 0) {
0827: return;
0828: }
0829:
0830: TraceEjb.swapper.log(BasicLevel.DEBUG, ejbname);
0831:
0832: // Set classloader for getting the right component context
0833: ClassLoader old = Thread.currentThread()
0834: .getContextClassLoader();
0835: Thread.currentThread().setContextClassLoader(myClassLoader());
0836:
0837: // Set the JNDI component context (for ejbStore)
0838: Context bnctx = setComponentContext();
0839:
0840: JEntitySwitch bs = null;
0841: for (Iterator i = clone.values().iterator(); i.hasNext();) {
0842: bs = (JEntitySwitch) i.next();
0843: bs.passivateIH(true, true);
0844: }
0845: // Reset old value for component context
0846: resetComponentContext(bnctx);
0847: Thread.currentThread().setContextClassLoader(old);
0848: }
0849:
0850: // --------------------------------------------------------------
0851: // PK list - JEntitySwitch cache Management
0852: // --------------------------------------------------------------
0853:
0854: /**
0855: * List of JEntitySwitch objects
0856: * At each PK is associated a JEntitySwitch object
0857: */
0858: private HashMap pklist = new HashMap();
0859:
0860: /**
0861: * dirty list of JEntitySwitch
0862: * Only used for CS policy (non transacted modifying methods)
0863: */
0864: private LinkedList dirtyList = new LinkedList();
0865:
0866: private boolean mustSyncDirtyEntities = false;
0867:
0868: /**
0869: * @return true if dirty list is not empty
0870: * Only used for CS policy (non transacted modifying methods)
0871: */
0872: public boolean dirtyInstances() {
0873: return mustSyncDirtyEntities;
0874: }
0875:
0876: /**
0877: * This field is used to generate an uid for unique automatic pk
0878: */
0879: private static final long DIZINIT = 1132241379300L;
0880: private int ucount = (int) (System.currentTimeMillis() - DIZINIT) / 100;
0881:
0882: /**
0883: * Calculate a new uid for automatic pk creation Used by JEntityCmpJdbc.vm
0884: * @return int (unique pk)
0885: */
0886: public int calculateAutomaticPk() {
0887: if (TraceEjb.isDebugIc()) {
0888: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname);
0889: }
0890: int uid;
0891: synchronized (this ) {
0892: uid = ucount++;
0893: }
0894: return uid;
0895: }
0896:
0897: /**
0898: * Encode PK. This does nothing, except in case of CMP2
0899: * @return String representation of the PK
0900: */
0901: public Serializable encodePK(Serializable pk) {
0902: return pk;
0903: }
0904:
0905: /**
0906: * Decode PK. This does nothing, except in case of CMP2
0907: * @return PK matching the String
0908: */
0909: public Serializable decodePK(Serializable strpk) {
0910: return strpk;
0911: }
0912:
0913: /**
0914: * get EJB by its PK
0915: * Creates if not exist yet.
0916: * @param pk The Primary Key Object
0917: * @return The JEntitySwitch matching the PK.
0918: */
0919: public synchronized JEntitySwitch getEJB(Object pk) {
0920:
0921: if (TraceEjb.isDebugIc()) {
0922: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname);
0923: }
0924:
0925: if (pk == null) {
0926: TraceEjb.logger.log(BasicLevel.ERROR, "pk null ???");
0927: return null;
0928: }
0929: JEntitySwitch bs = (JEntitySwitch) pklist.get(pk);
0930: if (bs == null) {
0931: bs = getJEntitySwitch();
0932: bs.init(this , pk);
0933: pklist.put(pk, bs);
0934: }
0935: return bs;
0936: }
0937:
0938: /**
0939: * get EJB by its PK. If not exist yet, map the EntitySwitch to the PK.
0940: * @param pk The Primary Key Object
0941: * @param bs the Entityswitch
0942: * @return The JEntitySwitch matching the PK, or null if none exist.
0943: */
0944: public synchronized JEntitySwitch existEJB(Object pk,
0945: JEntitySwitch bs) {
0946: if (pk == null) {
0947: TraceEjb.logger.log(BasicLevel.ERROR, "pk null ???");
0948: return null;
0949: }
0950: JEntitySwitch ret = (JEntitySwitch) pklist.get(pk);
0951: if (ret == null && bs != null) {
0952: // Bind new EntitySwitch while we got the lock.
0953: bs.init(this , pk);
0954: pklist.put(pk, bs);
0955: }
0956: return ret;
0957: }
0958:
0959: /**
0960: * rebind a PK with a JEntitySwitch (called by create methods)
0961: * @param tx current Transaction
0962: * @param bctx The EntityContext
0963: * @param pk The Primary Key Object
0964: * @return true if bs has been added to the PK list.
0965: */
0966: public boolean rebindEJB(Transaction tx, JEntityContext bctx,
0967: Object pk) {
0968: if (TraceEjb.isDebugIc()) {
0969: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname);
0970: }
0971:
0972: if (pk == null) {
0973: TraceEjb.logger.log(BasicLevel.ERROR, "pk null ???");
0974: return false;
0975: }
0976: JEntitySwitch bs = bctx.getEntitySwitch();
0977: JEntitySwitch old = null;
0978: synchronized (this ) {
0979: old = (JEntitySwitch) pklist.get(pk);
0980: }
0981: boolean ret = true;
0982: if (old != null) {
0983: // different cases:
0984: // - a create after a remove, in the same tx (or no tx)
0985: // - in CMP2: a "DuplicateKey".
0986: // - Bean removed by another server (bean shared)
0987:
0988: // Release completely the old EntitySwitch before binding the new one.
0989: // must be done outside the lock, to avoid a deadlock.
0990: ret = old.terminate(tx);
0991: }
0992: synchronized (this ) {
0993: bs.init(this , pk);
0994: pklist.put(pk, bs);
0995: }
0996: return ret;
0997: }
0998:
0999: /**
1000: * Bind a PK with a JEntitySwitch
1001: * @param pk The Primary Key Object
1002: * @param bs The JEntitySwitch
1003: */
1004: public synchronized void bindEJB(Object pk, JEntitySwitch bs) {
1005: if (pk == null) {
1006: TraceEjb.logger.log(BasicLevel.ERROR, "pk null ???");
1007: return;
1008: }
1009: if (TraceEjb.isDebugSwapper()) {
1010: TraceEjb.swapper.log(BasicLevel.DEBUG, ejbname);
1011: }
1012: bs.init(this , pk);
1013: pklist.put(pk, bs);
1014: }
1015:
1016: /**
1017: * This method allocates a new JEntitySwitch. But no association has been
1018: * done between the primary key and the new JEntitySwitch. Therefore the
1019: * initialisation is not done.
1020: * @return The JEntitySwitch.
1021: */
1022: public JEntitySwitch getJEntitySwitch() {
1023: switch (lockPolicy) {
1024: case EntityDesc.LOCK_CONTAINER_READ_UNCOMMITTED:
1025: return new JEntitySwitchCRU();
1026: case EntityDesc.LOCK_CONTAINER_READ_COMMITTED:
1027: return new JEntitySwitchCRC();
1028: case EntityDesc.LOCK_DATABASE:
1029: return new JEntitySwitchDB();
1030: case EntityDesc.LOCK_READ_ONLY:
1031: return new JEntitySwitchRO();
1032: case EntityDesc.LOCK_CONTAINER_READ_WRITE:
1033: return new JEntitySwitchCRW();
1034: case EntityDesc.LOCK_CONTAINER_SERIALIZED_TRANSACTED:
1035: return new JEntitySwitchCST();
1036: case EntityDesc.LOCK_CONTAINER_SERIALIZED:
1037: return new JEntitySwitchCS();
1038: default:
1039: TraceEjb.logger.log(BasicLevel.WARN,
1040: "lockPolicy not initilized");
1041: return new JEntitySwitchCS();
1042: }
1043: }
1044:
1045: /**
1046: * remove an EJB by its PK
1047: * @param pk The Primary Key Object
1048: */
1049: public synchronized void removeEJB(Object pk) {
1050: if (TraceEjb.isDebugSwapper()) {
1051: TraceEjb.swapper.log(BasicLevel.DEBUG, ejbname);
1052: }
1053: if (pk == null) {
1054: TraceEjb.logger.log(BasicLevel.ERROR, "pk null ???");
1055: return;
1056: }
1057: pklist.remove(pk);
1058: }
1059:
1060: /**
1061: * Register an EntitySwitch in the dirty list.
1062: * should be called each time a new instance is modified outside transaction
1063: * @param ejb The Entity Switch to be registered
1064: */
1065: public synchronized void registerEJB(JEntitySwitch ejb) {
1066: if (TraceEjb.isDebugSwapper()) {
1067: TraceEjb.swapper.log(BasicLevel.DEBUG, ejbname);
1068: }
1069: if (dirtyList.contains(ejb)) {
1070: TraceEjb.swapper.log(BasicLevel.ERROR, ejbname
1071: + " Elt already in the dirty list");
1072: return;
1073: }
1074: dirtyList.addLast(ejb);
1075: mustSyncDirtyEntities = true;
1076: }
1077:
1078: /**
1079: * Ask swapper to synchronize all dirty EntitySwitch
1080: * Only used for container-serialized policy
1081: */
1082: public void synchronizeEntities() {
1083: if (TraceEjb.isDebugSwapper()) {
1084: TraceEjb.swapper.log(BasicLevel.DEBUG, ejbname);
1085: }
1086: cont.registerBFS(this );
1087: }
1088:
1089: /**
1090: * Search if this transaction is blocked in a deadlock
1091: * @param suspect Transaction suspected to block everybody
1092: * @return True if a deadlock has been detected.
1093: */
1094: public boolean isDeadLocked(Transaction suspect) {
1095: // Get a clone of the complete list of PKs
1096: HashMap clone = null;
1097: synchronized (this ) {
1098: clone = (HashMap) pklist.clone();
1099: }
1100: JEntitySwitch es = null;
1101: // Build a list of all transactions that block "suspect" tx.
1102: ArrayList blktx = new ArrayList();
1103: Transaction testedtx = suspect;
1104: // Loop until a result is found (true or false)
1105: while (true) {
1106: for (Iterator i = clone.values().iterator(); i.hasNext();) {
1107: es = (JEntitySwitch) i.next();
1108: Transaction tx = es.getBlockingTx(testedtx);
1109: if (tx != null) {
1110: if (tx.equals(suspect)) {
1111: TraceEjb.synchro.log(BasicLevel.WARN,
1112: "Found a deadlock on :" + tx);
1113: // found a deadlock
1114: return true;
1115: }
1116: blktx.add(tx);
1117: }
1118: }
1119: if (blktx.size() == 0) {
1120: // no deadlock found.
1121: TraceEjb.synchro.log(BasicLevel.DEBUG,
1122: "No deadlock found");
1123: return false;
1124: }
1125: testedtx = (Transaction) blktx.remove(0);
1126: }
1127: }
1128:
1129: /**
1130: * Take a dump of current entity counters and return them
1131: * @return EntityCounters
1132: */
1133: public synchronized EntityCounters getEntityCounters() {
1134: EntityCounters ec = new EntityCounters();
1135: Collection coll = pklist.values();
1136: ec.pk = pklist.size();
1137: for (Iterator i = coll.iterator(); i.hasNext();) {
1138: JEntitySwitch es = (JEntitySwitch) i.next();
1139: switch (es.getState()) {
1140: case 0:
1141: ec.inTx++;
1142: break;
1143: case 1:
1144: ec.outTx++;
1145: break;
1146: case 2:
1147: ec.idle++;
1148: break;
1149: case 3:
1150: ec.passive++;
1151: break;
1152: case 4:
1153: ec.removed++;
1154: break;
1155: }
1156: }
1157: return ec;
1158: }
1159:
1160: // --------------------------------------------------------------
1161: // transaction association management
1162: // --------------------------------------------------------------
1163:
1164: /**
1165: * List of Transaction Listeners.
1166: * At each Transaction is associated a TxListener object
1167: */
1168: protected HashMap txlist = new HashMap();
1169:
1170: /**
1171: * synchronize data modified in this transaction.
1172: * this is necessary in case of finder method, because
1173: * ejb-ql looks for on disk.
1174: * @param tx the Transaction
1175: */
1176: public void syncForFind(Transaction tx) {
1177: if (TraceEjb.isDebugIc()) {
1178: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname);
1179: }
1180: if (tx == null) {
1181: if (lockPolicy == EntityDesc.LOCK_CONTAINER_SERIALIZED) {
1182: syncDirty(true);
1183: }
1184: } else {
1185: this .getContainer().storeAll(tx);
1186: }
1187: }
1188:
1189: /**
1190: * synchronize data modified in the current transaction.
1191: * this is necessary in case of select method, because
1192: * ejb-ql looks for on disk.
1193: */
1194: public void syncForSelect() {
1195: if (TraceEjb.isDebugIc()) {
1196: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname);
1197: }
1198: // First of all, get the current transaction
1199: Transaction currtx = null;
1200: try {
1201: currtx = tm.getTransaction();
1202: if (TraceEjb.isDebugTx()) {
1203: TraceEjb.tx.log(BasicLevel.DEBUG, "currtx=" + currtx);
1204: }
1205: } catch (SystemException e) {
1206: TraceEjb.logger.log(BasicLevel.ERROR,
1207: "system exception while getting transaction:", e);
1208: return;
1209: }
1210: // Sync dirty instances for the current transaction
1211: syncForFind(currtx);
1212: }
1213:
1214: /**
1215: * For storing entities modified in tx
1216: * @param tx current transaction
1217: * @see org.objectweb.jonas_ejb.container.BeanFactory#storeInstances(javax.transaction.Transaction)
1218: */
1219: public void storeInstances(Transaction tx) {
1220: TxListener txl = (TxListener) txlist.get(tx);
1221: if (txl != null) {
1222: txl.storeInstances();
1223: }
1224:
1225: }
1226:
1227: /**
1228: * Remove a Transaction Listener from the list.
1229: * @param tx the Transaction to remove
1230: */
1231: public void removeTxListener(Transaction tx) {
1232: if (TraceEjb.isDebugIc()) {
1233: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname);
1234: }
1235: txlist.remove(tx);
1236: }
1237:
1238: /**
1239: * Unregister a Context/Instance in the transaction.
1240: * @param tx current Transaction
1241: * @param ctx JEntityContext to be registered
1242: */
1243: public void unregisterContext(Transaction tx, JEntityContext ec)
1244: throws IllegalStateException {
1245: if (TraceEjb.isDebugIc()) {
1246: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname);
1247: }
1248: // Get the TXListener associated to this tx.
1249: TxListener txl = (TxListener) txlist.get(tx);
1250: if (txl == null) {
1251: return;
1252: }
1253: txl.removeInstance(ec);
1254: }
1255:
1256: /**
1257: * Register a Context/Instance in the transaction.
1258: * @param tx current Transaction
1259: * @param ctx JEntityContext to be registered
1260: * @return true if instance has been registered.
1261: */
1262: public boolean registerContext(Transaction tx, JEntityContext ec)
1263: throws IllegalStateException {
1264: if (TraceEjb.isDebugIc()) {
1265: TraceEjb.interp.log(BasicLevel.DEBUG, ejbname);
1266: }
1267:
1268: // Get the TXListener associated to this tx, or create it.
1269: TxListener txl = (TxListener) txlist.get(tx);
1270: if (txl == null) {
1271: txl = new TxListener(this , tx);
1272: // register it as Synchronization.
1273: try {
1274: tx.registerSynchronization(txl);
1275: } catch (RollbackException e) {
1276: if (TraceEjb.isVerbose()) {
1277: TraceEjb.logger
1278: .log(
1279: BasicLevel.WARN,
1280: ejbname
1281: + " transaction has been marked rollbackOnly",
1282: e);
1283: }
1284: } catch (SystemException e) {
1285: TraceEjb.logger.log(BasicLevel.ERROR, ejbname
1286: + " cannot register synchro ", e);
1287: // forget txl.
1288: throw new IllegalStateException(
1289: "cannot register synchro");
1290: }
1291: txlist.put(tx, txl);
1292: }
1293:
1294: // Add the JEntityContext
1295: txl.addInstance(ec);
1296: return true;
1297: }
1298:
1299: /**
1300: * Check Transaction before calling a method on a bean. For Entity beans,
1301: * the only possible case is "Container Managed Tx"
1302: * @param rctx The Request Context
1303: */
1304: public void checkTransaction(RequestCtx rctx) {
1305: checkTransactionContainer(rctx);
1306: }
1307:
1308: // -----------------------------------------------------------------------
1309: // Timer Service Implementation
1310: // ----------------------------------------------------------------------
1311:
1312: /**
1313: * Obtains the TimerService associated for this Bean
1314: * @return a JTimerService instance.
1315: */
1316: public TimerService getTimerService() {
1317: return this ;
1318: }
1319:
1320: /**
1321: * Illegal operation: timers are associated with a PK
1322: * @see javax.ejb.TimerService#createTimer(long, java.io.Serializable)
1323: */
1324: public Timer createTimer(long arg0, Serializable arg1)
1325: throws IllegalArgumentException, IllegalStateException,
1326: EJBException {
1327: throw new IllegalStateException(
1328: "timers are associated with a PK");
1329: }
1330:
1331: /**
1332: * Illegal operation: timers are associated with a PK
1333: /* (non-Javadoc)
1334: * @see javax.ejb.TimerService#createTimer(long, long, java.io.Serializable)
1335: */
1336: public Timer createTimer(long arg0, long arg1, Serializable arg2)
1337: throws IllegalArgumentException, IllegalStateException,
1338: EJBException {
1339: throw new IllegalStateException(
1340: "timers are associated with a PK");
1341: }
1342:
1343: /**
1344: * Illegal operation: timers are associated with a PK
1345: * @see javax.ejb.TimerService#createTimer(java.util.Date, java.io.Serializable)
1346: */
1347: public Timer createTimer(Date arg0, Serializable arg1)
1348: throws IllegalArgumentException, IllegalStateException,
1349: EJBException {
1350: throw new IllegalStateException(
1351: "timers are associated with a PK");
1352: }
1353:
1354: /**
1355: * Illegal operation: timers are associated with a PK
1356: * @see javax.ejb.TimerService#createTimer(java.util.Date, long, java.io.Serializable)
1357: */
1358: public Timer createTimer(Date arg0, long arg1, Serializable arg2)
1359: throws IllegalArgumentException, IllegalStateException,
1360: EJBException {
1361: throw new IllegalStateException(
1362: "timers are associated with a PK");
1363: }
1364:
1365: /**
1366: * Illegal operation: timers are associated with a PK
1367: * @see javax.ejb.TimerService#getTimers()
1368: */
1369: public Collection getTimers() throws IllegalStateException,
1370: EJBException {
1371: throw new IllegalStateException(
1372: "timers are associated with a PK");
1373: }
1374:
1375: }
|