JEntitySwitch.java

00001 
00026 package org.objectweb.jonas_ejb.container;
00027 
00028 import java.rmi.NoSuchObjectException;
00029 import java.rmi.RemoteException;
00030 
00031 import javax.ejb.EJBException;
00032 import javax.ejb.EntityBean;
00033 import javax.ejb.NoSuchObjectLocalException;
00034 import javax.ejb.ObjectNotFoundException;
00035 import javax.ejb.TimedObject;
00036 import javax.ejb.Timer;
00037 import javax.ejb.TimerService;
00038 import javax.transaction.Status;
00039 import javax.transaction.SystemException;
00040 import javax.transaction.Transaction;
00041 
00042 import org.objectweb.jonas_ejb.deployment.api.EntityDesc;
00043 import org.objectweb.jonas_ejb.deployment.api.MethodDesc;
00044 import org.objectweb.jonas_timer.TraceTimer;
00045 
00046 import org.objectweb.util.monolog.api.BasicLevel;
00047 
00059 public abstract class JEntitySwitch {
00060 
00064     protected JEntityFactory bf;
00065 
00069     protected Object pk = null;
00070 
00074     protected JEntityLocal local = null;
00075 
00079     protected JEntityRemote remote = null;
00080 
00084     protected long inactivityTimeout; // millisec.
00085 
00089     protected boolean shared;
00090 
00101     protected int lockpolicy;
00102 
00107     protected boolean txUpdates;
00108 
00112     protected TimerService myTimerService = null;
00113 
00117     protected int countIH = 0;
00118 
00122     protected int countIT = 0;
00123 
00127     protected int waiters = 0;
00128 
00133     protected boolean inDirtyList = false;
00134 
00140     protected boolean mustReload = false;
00141 
00146     protected boolean mustStore = false;
00147 
00152     protected boolean todiscard = false;
00153 
00154     // transaction on which a Context has been registered.
00155     // used only for policies CS and CRC
00156     protected Transaction runningtx = null;
00157 
00158     // transaction that has modified an instance
00159     // used only for policies DB and CRU
00160     protected Transaction writingtx = null;
00161 
00166     protected Transaction blockedtx = null;
00167 
00168     protected boolean isremoved = false;
00169 
00170     // ident is used mainly for debugging
00171     protected static int counter = 0;
00172 
00173     protected String ident;
00174 
00178     protected long timestamp = System.currentTimeMillis();
00179 
00180     private final static long FEW_MINUTS = 180000;
00181 
00182     private static final int MAX_NB_RETRY = 2;
00183 
00187     protected boolean lazyregister = false;
00188 
00193     protected boolean reentrant;
00194 
00195     abstract JEntityContext getContext4Tx(Transaction tx);
00196 
00197     abstract void setContext4Tx(Transaction tx, JEntityContext ctx);
00198 
00199     abstract void removeContext4Tx(Transaction tx);
00200 
00201     abstract protected void initpolicy(JEntityFactory bf);
00202 
00203     abstract public boolean passivateIH(boolean passivation);
00204 
00205     abstract public void endIH();
00206 
00207     abstract public void notifyWriting(Transaction tx, JEntityContext bctx);
00208 
00213     public JEntitySwitch() {
00214     }
00215 
00223     public void init(JEntityFactory bf, Object pk) {
00224         this.bf = bf;
00225         this.pk = pk;
00226         isremoved = false;
00227         if (pk == null) {
00228             TraceEjb.logger.log(BasicLevel.ERROR, ident + "Init Entity Switch with a null PK!");
00229             throw new EJBException("Init Entity Switch with a null PK!");
00230         }
00231 
00232         shared = bf.isShared();
00233         reentrant = bf.isReentrant();
00234         inactivityTimeout = bf.getInactivityTimeout() * 1000;
00235         initpolicy(bf);
00236 
00237         // generate new ident (debug)
00238         ident = "<" + counter + ":" + pk + ">";
00239         counter++;
00240 
00241         countIH = 0;
00242         countIT = 0;
00243         waiters = 0;
00244 
00245         // Create EJBObject if bean has a Remote Interface
00246         if (bf.getHome() != null) {
00247             try {
00248                 remote = ((JEntityHome) bf.getHome()).createRemoteObject();
00249                 remote.setEntitySwitch(this);
00250             } catch (RemoteException e) {
00251                 throw new EJBException("cannot create Remote Object", e);
00252             }
00253         }
00254 
00255         // Create EJBLocalObject if bean has a Local Interface
00256         if (bf.getLocalHome() != null) {
00257             local = ((JEntityLocalHome) bf.getLocalHome()).createLocalObject();
00258             local.setEntitySwitch(this);
00259         }
00260         // Set the timestamp to a big value because instance will be used.
00261         // This solve a problem in case of findAll() for example :
00262         // Avoids to discard instance just before it will be used.
00263         if (inactivityTimeout > 0) {
00264             timestamp = System.currentTimeMillis() + FEW_MINUTS;
00265             TraceTimer.logger.log(BasicLevel.DEBUG, ident + timestamp);
00266         }
00267     }
00268 
00272     public JEntityLocal getLocal() {
00273         return local;
00274     }
00275 
00279     public JEntityRemote getRemote() {
00280         return remote;
00281     }
00282 
00287     public TimerService getEntityTimerService() {
00288         // In case this object is used for a finder method,
00289         // pk should be null, and TimerService must not be retrieved.
00290         if (pk == null) {
00291             throw new java.lang.IllegalStateException();
00292         }
00293         if (myTimerService == null) {
00294             // TODO : Check that instance implements TimedObject ?
00295             myTimerService = new JTimerService(this);
00296         }
00297         return myTimerService;
00298     }
00299 
00304     public void notifyTimeout(Timer timer) {
00305         TraceTimer.logger.log(BasicLevel.DEBUG, ident);
00306 
00307         boolean committed = false;
00308         for (int nbretry = 0; ! committed && nbretry < MAX_NB_RETRY; nbretry++) {
00309             RequestCtx rctx = bf.preInvoke(bf.getTimerTxAttribute());
00310             try {
00311                 JEntityContext bctx = getICtx(rctx.currTx, false);
00312                 EntityBean eb = bctx.getInstance();
00313                 bf.checkSecurity(null);
00314                 if (eb instanceof TimedObject) {
00315                     TimedObject instance = (TimedObject) eb;
00316                     instance.ejbTimeout(timer);
00317                 } else {
00318                     throw new EJBException("The bean does not implement the `TimedObject` interface");
00319                 }
00320                 committed = (rctx.currTx == null) || 
00321                 (rctx.currTx.getStatus() != Status.STATUS_MARKED_ROLLBACK);
00322             } catch (EJBException e) {
00323                 rctx.sysExc = e;
00324                 throw e;
00325             } catch (RuntimeException e) {
00326                 rctx.sysExc = e;
00327                 throw new EJBException("RuntimeException thrown by an enterprise Bean", e);
00328             } catch (Error e) {
00329                 rctx.sysExc = e;
00330                 throw new EJBException("Error thrown by an enterprise Bean" + e);
00331             } catch (RemoteException e) {
00332                 rctx.sysExc = e;
00333                 throw new EJBException("Remote Exception raised:", e);
00334             } catch (SystemException e) {
00335                 rctx.sysExc = e;
00336                 throw new EJBException("Cannot get transaction status:", e);
00337             } finally {
00338                 try {
00339                     bf.postInvoke(rctx);
00340                 } finally {
00341                     if (rctx.sysExc != null) {
00342                         discardICtx(rctx.currTx);
00343                     } else {
00344                         releaseICtx(rctx.currTx);
00345                     }
00346                 }
00347             }
00348         }
00349     }
00350 
00354     public Object getPrimaryKey() {
00355         if (pk == null) {
00356             throw new java.lang.IllegalStateException();
00357         }
00358         return pk;
00359     }
00360 
00366     public void bindICtx(Transaction tx, JEntityContext bctx) {
00367         mapICtx(tx, bctx, true, false, false);
00368     }
00369 
00377     public synchronized boolean tryBindICtx(Transaction tx, JEntityContext bctx) throws ObjectNotFoundException {
00378 
00379         // Don't bind if already a context.
00380         if (getContext4Tx(tx) != null) {
00381             TraceEjb.context.log(BasicLevel.DEBUG, ident + "context already mapped!");
00382             if (getContext4Tx(tx).isMarkedRemoved()) {
00383                 TraceEjb.context.log(BasicLevel.DEBUG, ident + "Removed in the current tx");
00384                 throw new ObjectNotFoundException("Removed in the current tx");
00385             }
00386             // bctx will be released by caller. (See JEntityHome.vm)
00387             return false;
00388         }
00389 
00390         // No synchronization, since this is used for readonly methods (finder);
00391         bctx.initEntityContext(this);
00392         bctx.activate(true);
00393         setContext4Tx(tx, bctx); // after activate (CRU => full parallellism)
00394         if (!lazyregister) {
00395             if (tx == null) {
00396                 if (TraceEjb.isDebugSynchro()) TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "IH find");
00397             } else {
00398                 if (TraceEjb.isDebugSynchro()) TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "IT find");
00399                 registerCtx(tx, bctx);
00400             }
00401         }
00402         return true;
00403     }
00404 
00412     public JEntityContext getICtx(Transaction tx, JEntityContext newctx) {
00413         return mapICtx(tx, newctx, false, false, false);
00414     }
00415 
00422     public JEntityContext getICtx(Transaction tx, boolean checkr) {
00423         return mapICtx(tx, null, false, true, checkr);
00424     }
00425 
00432     public synchronized boolean terminate(Transaction tx) {
00433         TraceEjb.swapper.log(BasicLevel.DEBUG, ident);
00434         waitmyturn(tx);
00435         JEntityContext jec = getContext4Tx(tx);
00436         if (jec != null) {
00437             if (todiscard || jec.isMarkedRemoved()) {
00438                 discardContext(tx, true, true);
00439             } else {
00440                 try {
00441                     jec.storeIfModified();
00442                 } catch (Exception e) {
00443                     TraceEjb.logger.log(BasicLevel.ERROR, ident, "error while storing bean state:", e);
00444                 }
00445                 jec.passivate();
00446                 discardContext(tx, false, true);
00447             }
00448             if (waiters > 0) {
00449                 TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " notify");
00450                 notifyAll();
00451             }
00452         }
00453         return true;
00454     }
00455 
00456     abstract void waitmyturn(Transaction tx);
00457 
00467     public synchronized JEntityContext mapICtx(Transaction tx, JEntityContext bctx, boolean forced, boolean holdit, boolean checkreentrance) {
00468 
00469         // DEBUG only
00470         if (bf == null) {
00471             TraceEjb.synchro.log(BasicLevel.ERROR, "JEntitySwitch not initialized!");
00472             throw new EJBException("JEntitySwitch not initialized");
00473         }
00474         if (TraceEjb.isDebugSynchro()) {
00475             TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " tx=" + tx);
00476         }
00477 
00478         // Check non-reentrance (See spec EJB 2.1 section 12.1.13)
00479         if (!reentrant && checkreentrance) {
00480             if (runningtx != null && countIT > 0 && tx != null && tx.equals(runningtx)) {
00481                 throw new EJBException("non-reentrant bean accessed twice in same transaction");
00482             }
00483             if (tx == null && countIH > 0) {
00484                 throw new EJBException("non-reentrant bean accessed twice outside transaction");
00485             }
00486         }
00487         // synchro (lock-policy dependant)
00488         waitmyturn(tx);
00489 
00490         // Set the timestamp to a big value because instance will be used.
00491         if (inactivityTimeout > 0) {
00492             timestamp = System.currentTimeMillis() + FEW_MINUTS;
00493             TraceTimer.logger.log(BasicLevel.DEBUG, ident + timestamp);
00494         }
00495 
00496         // Choose the context to use.
00497         boolean newtrans = false;
00498         JEntityContext jec = getContext4Tx(tx);
00499         if (forced) {
00500             // If the new context is enforced, we must first release the older
00501             if (jec != null) {
00502                 TraceEjb.context.log(BasicLevel.DEBUG, ident + "new context is enforced!");
00503                 discardContext(tx, false, true);
00504             }
00505             jec = bctx;
00506             setContext4Tx(tx, jec);
00507             jec.initEntityContext(this);
00508             newtrans = true;
00509             isremoved = false;  // in case of create following a remove
00510         } else {
00511             // First check if bean still exists
00512             if (isremoved) {
00513                 TraceEjb.logger.log(BasicLevel.WARN, ident + " has been removed.");
00514                 throw new NoSuchObjectLocalException("Try to access a bean previously removed");
00515             }
00516             if (jec != null) {
00517                 if (todiscard) {
00518                     TraceEjb.logger.log(BasicLevel.WARN, ident + " has been discarded.");
00519                     throw new NoSuchObjectLocalException("Try to access a bean previously discarded");
00520                 }
00521                 // Reuse the Context for this transaction.
00522                 // If a context was supplied, release it first.
00523                 if (bctx != null) {
00524                     if (TraceEjb.isDebugContext())
00525                             TraceEjb.context.log(BasicLevel.DEBUG, ident + "a context was supplied!");
00526                     bf.releaseJContext(bctx);
00527                 }
00528                 // In case the same instance is used for all tx, must check if
00529                 // new one. For LOCK_DATABASE policy, it cannot be a new tx.
00530                 if (runningtx == null && lockpolicy != EntityDesc.LOCK_DATABASE) {
00531                     newtrans = true;
00532                 }
00533                 jec.reuseEntityContext(newtrans);
00534             } else {
00535                 if (bctx != null) {
00536                     jec = bctx;
00537                 } else {
00538                     // no Context available : get one from the pool.
00539                     jec = (JEntityContext) bf.getJContext(this);
00540                 }
00541                 jec.initEntityContext(this);
00542                 jec.activate(true);
00543                 setContext4Tx(tx, jec); // after activate
00544                 newtrans = true;
00545             }
00546         }
00547 
00548         if (tx != null) {
00549             if (holdit) {
00550                 countIT++;
00551             }
00552             // Register Context now, except if no new transaction
00553             if (newtrans && !lazyregister) {
00554                 try {
00555                     registerCtx(tx, jec);
00556                     if (TraceEjb.isDebugSynchro())
00557                         TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "mapICtx IT: new tx, registerSynchronization");
00558                 } catch (IllegalStateException e) {
00559                     TraceEjb.synchro.log(BasicLevel.WARN, ident + "mapICtx IT: not registered!");
00560                 }
00561             }
00562         } else {
00563             if (holdit) {
00564                 countIH++;
00565                 if (TraceEjb.isDebugSynchro())
00566                         TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "mapICtx IH count=" + countIH);
00567                 if ((shared || mustReload) && countIH == 1) {
00568                     // reload state that could have been modified by
00569                     // transactions.
00570                     jec.activate(false);
00571                     mustReload = false;
00572                 }
00573             }
00574             if (!inDirtyList && !txUpdates) {
00575                 inDirtyList = true;
00576                 bf.registerEJB(this);
00577             }
00578         }
00579 
00580         return jec;
00581     }
00582 
00583     public Transaction getBlockedTx() {
00584         return blockedtx;
00585     }
00586 
00587     public Transaction getBlockingTx() {
00588         return runningtx;
00589     }
00590 
00595     public synchronized void releaseICtx(Transaction tx) {
00596 
00597         if (tx == null) {
00598             // ------------- IH end -------------
00599             countIH--;
00600             if (TraceEjb.isDebugSynchro())
00601                     TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "releaseICtx IH count=" + countIH);
00602             if (countIH == 0) {
00603                 JEntityContext jec = getContext4Tx(tx);
00604                 if (jec == null) {
00605                     TraceEjb.context.log(BasicLevel.ERROR, ident + " No context!");
00606                     Thread.dumpStack();
00607                     return;
00608                 }
00609                 if (todiscard || jec.isMarkedRemoved()) {
00610                     // TODO why test todiscard here ?
00611                     discardContext(tx, true, true);
00612                 } else {
00613                     if (mustStore) {
00614                         try {
00615                             jec.storeIfModified();
00616                         } catch (EJBException e) {
00617                             TraceEjb.logger.log(BasicLevel.WARN, ident + " ejbexception:" + e);
00618                         }
00619                         mustStore = false;
00620                     }
00621                 }
00622                 if (waiters > 0) {
00623                     TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " notify");
00624                     notifyAll();
00625                 }
00626             }
00627         } else {
00628             // ------------- IT end -------------
00629             // nothing to do now. Wait for tx completed.
00630             if (TraceEjb.isDebugSynchro()) TraceEjb.synchro.log(BasicLevel.DEBUG, ident + "releaseICtx IT");
00631             countIT--;
00632         }
00633     }
00634 
00640     public synchronized void discardICtx(Transaction tx) {
00641         if (tx == null) {
00642             countIH--;
00643             if (countIH == 0) {
00644                 discardContext(tx, false, false);
00645                 return;
00646             }
00647         } else {
00648             countIT--;
00649             if (runningtx == null) {
00650                 // The transaction has already been committed.
00651                 return;
00652             }
00653         }
00654         // to be done later.
00655         todiscard = true;
00656     }
00657 
00664     public synchronized void txCompleted(Transaction tx, boolean committed) {
00665         JEntityContext jec = getContext4Tx(tx);
00666         if (jec == null) {
00667             TraceEjb.context.log(BasicLevel.ERROR, ident + " No context for this tx");
00668             return;
00669         }
00670         runningtx = null;
00671         if (writingtx != null && tx.equals(writingtx)) {
00672             writingtx = null;
00673         }
00674 
00675         if (TraceEjb.isDebugSynchro()) TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " txCompleted " + committed);
00676 
00677         // The create was eventually rolled back or instance has been discarded.
00678         // We must remove PK entry.
00679         if (todiscard || (jec.isNewInstance() && !committed)) {
00680             discardContext(tx, !todiscard, false);
00681             return;
00682         }
00683 
00684         if (jec.isMarkedRemoved()) {
00685             if (TraceEjb.isDebugContext()) TraceEjb.context.log(BasicLevel.DEBUG, ident + "remove!");
00686             discardContext(tx, committed, true);
00687         } else {
00688             if (shared || !committed) {
00689                 // TODO read-only policy: keep instance until a specified timeout.
00690                 // LOCK_DATABASE policy should go here (shared = true)
00691                 if (TraceEjb.isDebugContext()) TraceEjb.context.log(BasicLevel.DEBUG, jec + " passivated!");
00692                 jec.passivate();
00693                 if (committed) {
00694                     // context is discarded if transaction rolled back
00695                     bf.releaseJContext(jec);
00696                 }
00697                 removeContext4Tx(tx);
00698             } else {
00699                 // do this before reallocating the Context for another tx
00700                 // (inside this lock)
00701                 jec.detachTx();
00702             }
00703             // If special instance for no transactional accesses, it must be
00704             // reloaded
00705             if (lockpolicy == EntityDesc.LOCK_CONTAINER_READ_COMMITTED || lockpolicy == EntityDesc.LOCK_DATABASE) {
00706                 mustReload = true;
00707             }
00708         }
00709         if (waiters > 0) {
00710             TraceEjb.synchro.log(BasicLevel.DEBUG, ident + " notify");
00711             notifyAll();
00712         }
00713     }
00714 
00722     protected void registerCtx(Transaction tx, JEntityContext bctx) {
00723         // register this context to the transaction, as a Synchronization.
00724         try {
00725             if (bf.registerContext(tx, bctx)) {
00726                 TraceEjb.context.log(BasicLevel.DEBUG, bctx + "registered!");
00727                 bctx.setRunningTx(tx);
00728                 runningtx = tx;
00729             } else {
00730                 TraceEjb.context.log(BasicLevel.WARN, bctx + "could not be registered!");
00731             }
00732         } catch (IllegalStateException e) {
00733             TraceEjb.logger.log(BasicLevel.ERROR, ident + "Transaction is in an illegal state");
00734             throw e;
00735         }
00736     }
00737 
00744     protected void discardContext(Transaction tx, boolean forgetpk, boolean pool) {
00745         TraceEjb.context.log(BasicLevel.DEBUG, "");
00746         JEntityContext jec = getContext4Tx(tx);
00747         if (jec != null) {
00748             if (pool) {
00749                 bf.releaseJContext(jec);
00750             }
00751             removeContext4Tx(tx);
00752         }
00753         if (forgetpk) {
00754             if (remote != null) {
00755                 try {
00756                     remote.unexportObject();
00757                 } catch (NoSuchObjectException e) {
00758                     TraceEjb.logger.log(BasicLevel.ERROR, ident + " unexport entity failed: " + e);
00759                 }
00760             }
00761             bf.removeEJB(getPrimaryKey());
00762 
00763             // Cancel all timers for this bean (= remove the TimerService)
00764             if (myTimerService != null) {
00765                 ((JTimerService) myTimerService).cancelAllTimers();
00766                 myTimerService = null;
00767             }
00768             // this Entity switch must not be used any longer.
00769             isremoved = true;
00770         }
00771         todiscard = false;  // done.
00772     }
00773 
00777     public int getPolicy() {
00778         return lockpolicy;
00779     }
00780 
00786     abstract public int getState();
00790     public JFactory getBeanFactory() {
00791         return bf;
00792     }
00793 
00794 }

Generated on Tue Feb 15 15:05:37 2005 for JOnAS by  doxygen 1.3.9.1