KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openejb > core > entity > EntityInstanceManager


1 /**
2  * Redistribution and use of this software and associated documentation
3  * ("Software"), with or without modification, are permitted provided
4  * that the following conditions are met:
5  *
6  * 1. Redistributions of source code must retain copyright
7  * statements and notices. Redistributions must also contain a
8  * copy of this document.
9  *
10  * 2. Redistributions in binary form must reproduce the
11  * above copyright notice, this list of conditions and the
12  * following disclaimer in the documentation and/or other
13  * materials provided with the distribution.
14  *
15  * 3. The name "Exolab" must not be used to endorse or promote
16  * products derived from this Software without prior written
17  * permission of Exoffice Technologies. For written permission,
18  * please contact info@exolab.org.
19  *
20  * 4. Products derived from this Software may not be called "Exolab"
21  * nor may "Exolab" appear in their names without prior written
22  * permission of Exoffice Technologies. Exolab is a registered
23  * trademark of Exoffice Technologies.
24  *
25  * 5. Due credit should be given to the Exolab Project
26  * (http://www.exolab.org/).
27  *
28  * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29  * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
32  * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39  * OF THE POSSIBILITY OF SUCH DAMAGE.
40  *
41  * Copyright 1999 (C) Exoffice Technologies Inc. All Rights Reserved.
42  *
43  * $Id: EntityInstanceManager.java 1096 2004-03-26 21:41:16Z dblevins $
44  */

45 package org.openejb.core.entity;
46
47 import java.util.HashMap JavaDoc;
48 import java.util.Hashtable JavaDoc;
49 import java.util.Properties JavaDoc;
50
51 import javax.ejb.EntityBean JavaDoc;
52 import javax.transaction.Transaction JavaDoc;
53
54 import org.openejb.ApplicationException;
55 import org.openejb.OpenEJB;
56 import org.openejb.OpenEJBException;
57 import org.openejb.core.DeploymentInfo;
58 import org.openejb.core.EnvProps;
59 import org.openejb.core.Operations;
60 import org.openejb.core.ThreadContext;
61 import org.openejb.util.LinkedListStack;
62 import org.openejb.util.Logger;
63 import org.openejb.util.SafeProperties;
64 import org.openejb.util.SafeToolkit;
65 import org.openejb.util.Stack;
66 /**
67  * One instance of this class is used with each EntityContainer. It is responsible for managing
68  * the Method Ready and Transaction Ready pools where bean instances are stored between client
69  * calls.
70  * <p>
71  * To obtain an instance the obtainInstance( ) method must be called with the primary key and
72  * bean type to obtained. The bean instance will be returned form the method ready pool if its not already
73  * enrolled in the current transaction, or from the transaction ready pool if it is.
74  * <p>
75  * If the bean identity requested is registered with a different tx, a different bean instance is returned
76  * which results in both transaction accessing the same bean identity, but using different bean
77  * instances.
78  * <p>
79  * The poolInstance( ) method is invoked when the client call is finished using the bean instance.
80  * the instance will be returned to the transaction ready pool if its registered with a transaction or
81  * it will be placed in the method ready pool if its not.
82  * <p>
83  * The freeInstance( ) method is invoked in an EJBException or some other RuntimeException occurs. In these
84  * situations, the bean instance must be de-registered from the transaction and de-referenced for garbage collection.
85  * the freeInstance( ) handles these operations.
86  * <p>
87  * This class automatically handles transferring bean instances to the Transaction Ready Pool and transferring
88  * them back to the Method Ready pool as necessary.
89  * <p>
90  * This class also handles the ejbStore operations by registering the instance with the transaction. The ejbStore
91  * Operation doesn't not need to be executed by the container. The container is, however, responsible for the ejbLoad method.
92  */

93 public class EntityInstanceManager {
94     
95     /* The default size of the bean pools. Every bean class gets its own pool of this size */
96     protected int poolsize = 0;
97     /* The container that owns this InstanceManager. Its needed to use the invoke() method
98        in the obtainInstance( ), which is needed for transactionally safe invokation.
99     */

100     protected EntityContainer container;
101     /*
102     * Every entity that is registered with a transaciton is kept in this pool until the tx
103     * completes. The pool contains SyncronizationWrappers (each holding a reference) keyed
104     * by using an instance of the inner Key class. The Key class is a compound key composed
105     * of the tx, deployment, and primary key identifiers.
106     */

107     protected Hashtable JavaDoc txReadyPool = new Hashtable JavaDoc( );
108     /*
109     * contains a collection of LinkListStacks indexed by deployment id. Each indexed stack
110     * represents the method ready pool of for that class.
111     */

112     protected HashMap JavaDoc poolMap = null;
113     
114     public Logger logger = Logger.getInstance( "OpenEJB", "org.openejb.util.resources" );
115
116     protected SafeToolkit toolkit = SafeToolkit.getToolkit("EntityInstanceManager");
117     /******************************************************************
118                         CONSTRUCTOR METHODS
119     *******************************************************************/

120     public EntityInstanceManager( ){
121     }
122
123     public void init(EntityContainer myContainer, HashMap JavaDoc deployments, Properties JavaDoc props)
124     throws OpenEJBException{
125
126         SafeProperties safeProps = toolkit.getSafeProperties(props);
127         poolsize = safeProps.getPropertyAsInt(EnvProps.IM_POOL_SIZE, 100);
128         container = myContainer;
129         
130         poolMap = new HashMap JavaDoc();// put size in later
131
java.util.Iterator JavaDoc iterator = deployments.values().iterator();
132         while(iterator.hasNext()){
133              poolMap.put(((DeploymentInfo)iterator.next()).getDeploymentID(),new LinkedListStack(poolsize/2));
134         }
135         
136     }
137
138     /**********************************************************************
139             InstanceManager INTERFACE METHODS
140     ***********************************************************************/

141    /**
142     * Obtains a bean instance from the either the method ready pool or the Tx method ready pool. (Tx == Transaction)
143     * An entity bean is obtained from the ready pool if its not enrolled in the current tx.
144     * An entity bean is obtained from the tx ready pool if it is enrolled in the current tx.
145     * A tx ready entity can only be obtained if:
146     * a: It not currently servicing a thread in that tx.
147     * b: It allows for reentrant access.
148     * Reentrant beans are accessed by multiple threads and are therefore unsafe and must be
149     * coded to be multi-threaded.
150     *
151     * If the primaryKey is null when the method is invoked, the bean will be retrived from the
152     * MethodReady pool and will not be associated with the current tx
153     *
154     * The call method is required for automatically peforming the ejbLoad when a bean instance is first
155     * registered with a transaction.
156     */

157     public EntityBean JavaDoc obtainInstance(ThreadContext callContext)
158     throws OpenEJBException{
159         Transaction JavaDoc currentTx = null;
160         try{
161         currentTx = OpenEJB.getTransactionManager().getTransaction();
162         }catch(javax.transaction.SystemException JavaDoc se){
163             logger.error("Transaction Manager getTransaction() failed.", se);
164             throw new org.openejb.SystemException("TransactionManager failure");
165         }
166         
167         Object JavaDoc primaryKey = callContext.getPrimaryKey();// null if its a servicing a home methods (create, find, ejbHome)
168
if(currentTx != null && primaryKey != null){// primkey is null if create operation is called
169
Key key = new Key(currentTx, callContext.getDeploymentInfo().getDeploymentID(),primaryKey);
170             SyncronizationWrapper wrapper = (SyncronizationWrapper)txReadyPool.get(key);
171             
172             if(wrapper != null){// if true, the requested bean instance is already enrolled in a transaction
173

174                 if( !wrapper.isAssociated()){// is NOT associated
175
/*
176                     * If the bean identity was removed (via ejbRemove()) within the same transaction,
177                     * then it's SynchronizationWrapper will be in the txReady pool but marked as disassociated.
178                     * This allows us to prevent a condition where the caller removes the bean and then attempts to
179                     * call a business method on that bean within the same transaction. After a bean is removed any
180                     * subsequent invocations on that bean with the same transaction should throw a NoSuchEntityException.
181                     * its likely that the application server would have already made the reference invalid, but this bit of
182                     * code is an extra precaution.
183                     */

184                     throw new org.openejb.InvalidateReferenceException(new javax.ejb.NoSuchEntityException JavaDoc("Entity not found: "+primaryKey));
185                 }else if(callContext.getCurrentOperation() == Operations.OP_REMOVE){
186                     /*
187                     * To avoid calling ejbStore( ) on a bean that after its removed, we can not delegate
188                     * the wrapper is marked as disassociated from the transaction to avoid processing the
189                     * beforeCompletion( ) method on the SynchronizationWrapper object.
190                     */

191                     wrapper.disassociate();
192                 }
193                 
194                 if(wrapper.isAvailable()){
195                     // isAvailable = true means that the bean is in the txReadyPool and is avaiable to continue servicing
196
// the same transaction.
197
return wrapper.getEntityBean();
198                 }else{
199                     // isAvailable = false means that the bean instance is currently servicing this transaction. Asking for it now indicates that
200
// retentrant access requested. Two clients attempting to access the same bean instance in the same transaction.
201
org.openejb.core.DeploymentInfo depInfo = (org.openejb.core.DeploymentInfo)callContext.getDeploymentInfo();
202                     if(depInfo.isReentrant()){
203                         /*
204                          * If the bean is declared as reentrant then the instance may be accessed
205                          * by more then one thread at a time. This is one of the reasons that reentrancy
206                          * is bad. In this case beans must be programmed to be multi threaded. The other reason
207                          * reentrancy is bad has to do with transaction isolation. Multiple instances writing to
208                          * the same database records will inevitably cancel out previous writes within the same tx.
209                          *
210                          * In the future we may change this to return a new instance of the bean and to
211                          * link it and its wrapper to the original wrapper, but for now we choose this strategy because
212                          * its simpler to implement.
213                         */

214                         return wrapper.getEntityBean();
215                     }else
216                         throw new org.openejb.ApplicationException(new java.rmi.RemoteException JavaDoc("Attempted reentrant access. Bean is not reentrant"));
217                 }
218             }
219             else{
220                 /*
221                 * If no synchronized wrapper for the key exists
222                 * Then the bean entity is being access by this transaction for the first time,
223                 * so it needs to be enrolled in the transaction.
224                 */

225                 EntityBean JavaDoc bean = getPooledInstance(callContext);
226                 wrapper = new SyncronizationWrapper(bean, key, false, callContext);
227                     
228                 if(callContext.getCurrentOperation()== Operations.OP_REMOVE){
229                     /*
230                     * To avoid calling ejbStore( ) on a bean that after its removed, we can not delegate
231                     * the wrapper is marked as disassociated from the transaction to avoid processing the
232                     * beforeCompletion( ) method on the SynchronizationWrapper object.
233                     *
234                     * We have to still use a wrapper so we can detect when a business method is called after
235                     * a ejbRemove() and act to prevent it from being processed.
236                     */

237                     wrapper.disassociate();
238                 }
239                     
240                 try{
241                     currentTx.registerSynchronization(wrapper);
242                 }catch(javax.transaction.SystemException JavaDoc se){
243                     logger.error("Transaction Manager registerSynchronization() failed.", se);
244                     throw new org.openejb.SystemException(se);
245                 }catch(javax.transaction.RollbackException JavaDoc re){
246                     throw new org.openejb.ApplicationException(new javax.transaction.TransactionRolledbackException JavaDoc(re.getMessage()));
247                 }
248                 loadingBean(bean, callContext);
249                 byte orginalOperation = callContext.getCurrentOperation();
250                 callContext.setCurrentOperation(org.openejb.core.Operations.OP_LOAD);
251                 try{
252                     bean.ejbLoad();
253                 }catch(Exception JavaDoc e){
254                     logger.error("Exception encountered during ejbLoad():", e);
255                     // this will always be handled by the invoke( ) method
256
throw new org.openejb.OpenEJBException(e);
257                 }finally {
258                 callContext.setCurrentOperation(orginalOperation);
259                 }
260                 txReadyPool.put(key, wrapper);
261
262                 return bean;
263             }
264         }else{ /*
265                 If no transaction is associated with the thread or if its a create, find or home method (primaryKey == null), then no synchronized wrapper is needed.
266                 if bean instance is used for a create method then a syncrhonziation wrapper may be assigned
267                 when the bean is returned to the pool -- depending on if the tx is a client initiated or container initiated.
268                 */

269             return getPooledInstance(callContext);
270         }
271     }
272
273     /**
274         * called prior to invoking ejbLoad on the bean. e.g. to give the CMP container
275      * a chance to load the persistent state
276      */

277     protected void loadingBean(javax.ejb.EntityBean JavaDoc bean, org.openejb.core.ThreadContext callContext) throws OpenEJBException {
278     }
279
280     protected void reusingBean(javax.ejb.EntityBean JavaDoc bean, org.openejb.core.ThreadContext callContext) throws OpenEJBException {
281     }
282     
283     /**
284     * Obtains a bean instance from the proper bean pool. Each bean class has its own pool
285     * of instances. If the bean class' pool is empty, a new Instance is created.
286     *
287     * If a bean instance is not avaible one will be created.
288     */

289     protected EntityBean JavaDoc getPooledInstance(ThreadContext callContext)
290     throws org.openejb.OpenEJBException{
291         DeploymentInfo deploymentInfo = callContext.getDeploymentInfo();
292         Stack methodReadyPool = (Stack)poolMap.get(deploymentInfo.getDeploymentID());
293         if(methodReadyPool==null)
294             throw new org.openejb.SystemException("Invalid deployment id "+deploymentInfo.getDeploymentID()+" for this container");
295             
296         EntityBean JavaDoc bean = (EntityBean JavaDoc)methodReadyPool.pop();
297         if(bean == null){
298             try{
299                 bean = (EntityBean JavaDoc)deploymentInfo.getBeanClass().newInstance();
300             }catch(Exception JavaDoc e){
301                 logger.error("Bean instantiation failed for class "+deploymentInfo.getBeanClass(), e);
302                 throw new org.openejb.SystemException(e);
303             }
304             // setEntityContext needs to be invoked, so need a special MethodInvocation
305
// that specifies the setEntityContext on the EntityBean interface with
306
// one argument, entity context
307

308             byte currentOp = callContext.getCurrentOperation();
309             callContext.setCurrentOperation(org.openejb.core.Operations.OP_SET_CONTEXT);
310
311             try{
312                 /*
313                 * setEntityContext executes in an unspecified transactional context. In this case we choose to
314                 * allow it to have what every transaction context is current. Better then suspending it
315                 * unnecessarily.
316                 *
317                 * We also chose not to invoke EntityContainer.invoke( ) method, which duplicate the exception handling
318                 * logic but also attempt to manage the begining and end of a transaction. It its a container managed transaciton
319                 * we don't want the TransactionScopeHandler commiting the transaction in afterInvoke() which is what it would attempt
320                 * to do.
321                 */

322                 bean.setEntityContext((javax.ejb.EntityContext JavaDoc)callContext.getDeploymentInfo().getEJBContext());
323             }catch(java.lang.Exception JavaDoc e){
324                 /*
325                 * The EJB 1.1 specification does not specify how exceptions thrown by setEntityContext impact the
326                 * transaction, if there is one. In this case we choose the least disruptive operation, throwing an
327                 * application exception and NOT automatically marking the transaciton for rollback.
328                 */

329                 logger.error("Bean callback method failed ", e);
330                 throw new org.openejb.ApplicationException(e);
331             }finally{
332                 callContext.setCurrentOperation(currentOp);
333             }
334         } else {
335             reusingBean(bean, callContext);
336         }
337
338     if( ( callContext.getCurrentOperation()== org.openejb.core.Operations.OP_BUSINESS) ||
339         ( callContext.getCurrentOperation()== org.openejb.core.Operations.OP_REMOVE ) )
340     {
341             /*
342             * When a bean is retrieved from the bean pool to service a client's business method request it must be
343             * notified that its about to enter service by invoking its ejbActivate( ) method. A bean instance
344             * does not have its ejbActivate() invoked when:
345             * 1. Its being retreived to service an ejbCreate()/ejbPostCreate().
346             * 2. Its being retrieved to service an ejbFind method.
347             * 3. Its being retrieved to service an ejbRemove() method.
348             * See section 9.1.4 of the EJB 1.1 specification.
349             */

350             byte currentOp = callContext.getCurrentOperation();
351
352             callContext.setCurrentOperation(org.openejb.core.Operations.OP_ACTIVATE);
353             try{
354                 /*
355                 In the event of an exception, OpenEJB is required to log the exception, evict the instance,
356                 and mark the transaction for rollback. If there is a transaction to rollback, then the a
357                 javax.transaction.TransactionRolledbackException must be throw to the client.
358                 See EJB 1.1 specification, section 12.3.2
359                 */

360                  bean.ejbActivate();
361             }catch(Throwable JavaDoc e){
362                 logger.error("Encountered exception during call to ejbActivate()", e);
363                 try{
364                     Transaction JavaDoc tx = OpenEJB.getTransactionManager().getTransaction();
365                     if(tx!=null){
366                         tx.setRollbackOnly();
367                         throw new ApplicationException(new javax.transaction.TransactionRolledbackException JavaDoc("Reflection exception thrown while attempting to call ejbActivate() on the instance. Exception message = "+e.getMessage()));
368                     }
369                 }catch(javax.transaction.SystemException JavaDoc se){
370                     logger.error("Transaction Manager getTransaction() failed.", se);
371                     throw new org.openejb.SystemException(se);
372                 }
373                 throw new ApplicationException(new java.rmi.RemoteException JavaDoc("Exception thrown while attempting to call ejbActivate() on the instance. Exception message = "+e.getMessage()));
374             }finally{
375                 callContext.setCurrentOperation(currentOp);
376             }
377             
378         }
379         return bean;
380     }
381     /**
382     * Returns a bean instance to the method ready or tx method ready pool.
383     * A bean is returned to the tx method ready pool if the current thread is associated with a transaction.
384     * If the current thread is not associated with a transaction then the bean is returned to the Method Ready pool
385     * If the primary key is null, this would indicate that the returning bean instance was used for a find or home
386     * business methods -- in this case the bean is returned to the MethodReady pool even if the current thread is associated
387     * with a transaction.
388     */

389     public void poolInstance(ThreadContext callContext,EntityBean JavaDoc bean)
390     throws OpenEJBException{
391         if(bean==null) {
392             // this happens when ejbLoad fails
393
return;
394         }
395         Object JavaDoc primaryKey = callContext.getPrimaryKey();// null if servicing a home ejbFind or ejbHome method.
396
Transaction JavaDoc currentTx = null;
397         try{
398         currentTx = OpenEJB.getTransactionManager().getTransaction();
399         }catch(javax.transaction.SystemException JavaDoc se){
400             logger.error("Transaction Manager getTransaction() failed.", se);
401             throw new org.openejb.SystemException("TransactionManager failure");
402         }
403         if(currentTx != null && primaryKey != null){// primary key is null for find and home methods
404
Key key = new Key(currentTx, callContext.getDeploymentInfo().getDeploymentID(),primaryKey);
405             SyncronizationWrapper wrapper = (SyncronizationWrapper)txReadyPool.get(key);
406             if(wrapper != null){
407                 if(callContext.getCurrentOperation()== Operations.OP_REMOVE){
408                     /*
409                     * The bean is being returned to the pool after it has been removed. Its
410                     * important at this point to mark the bean as disassociated to prevent
411                     * it's ejbStore method from bean called (see SynchronizationWrapper.beforeCompletion() method)
412                     * and that subsequent methods can not be invoked on the bean identity (see obtainInstance() method).
413                     */

414                     wrapper.disassociate();
415                     /*
416                     * If the bean has been removed then the bean instance is no longer needed and can return to the methodReadyPool
417                     * to service another identity.
418                     */

419                     Stack methodReadyPool = (Stack)poolMap.get(callContext.getDeploymentInfo().getDeploymentID());
420                     methodReadyPool.push(bean);
421                 }else
422                     wrapper.setEntityBean(bean);
423             }else{
424                 /*
425                 A wrapper will not exist if the bean is being returned after a create operation.
426                 In this case the transaction scope is broader then the create method itself; its a client
427                 initiated transaction, so the bean must be registered with the tranaction and moved to the
428                 tx ready pool
429                 */

430                 
431                 wrapper = new SyncronizationWrapper(bean, key, true, callContext);
432                 
433                 try{
434                     currentTx.registerSynchronization(wrapper);
435                 }catch(javax.transaction.SystemException JavaDoc se){
436                     logger.error("Transaction Manager registerSynchronization() failed.", se);
437                     throw new org.openejb.SystemException(se);
438                 }catch(javax.transaction.RollbackException JavaDoc re){
439                     throw new org.openejb.ApplicationException(new javax.transaction.TransactionRolledbackException JavaDoc(re.getMessage()));
440                 }
441                   
442                 txReadyPool.put(key, wrapper);
443             }
444         }else{
445             /*
446             If there is no transaction associated with the thread OR if the operation was a find or home method (PrimaryKey == null)
447             Then the bean instance is simply returned to the methodReady pool
448             */

449             
450             if(primaryKey !=null && callContext.getCurrentOperation()!= Operations.OP_REMOVE){
451                 /*
452                 * If the bean has a primary key; And its not being returned following a remove operation;
453                 * then the bean is being returned to the method ready pool after successfully executing a business method or create
454                 * method. In this case we need to call the bean instance's ejbPassivate before returning it to the pool per EJB 1.1
455                 * Section 9.1.
456                 */

457                 byte currentOp = callContext.getCurrentOperation();
458
459                 callContext.setCurrentOperation(org.openejb.core.Operations.OP_PASSIVATE);
460                 
461                 
462                 try{
463                     /*
464                     In the event of an exception, OpenEJB is required to log the exception, evict the instance,
465                     and mark the transaction for rollback. If there is a transaction to rollback, then the a
466                     javax.transaction.TransactionRolledbackException must be throw to the client.
467                     See EJB 1.1 specification, section 12.3.2
468                     */

469                     bean.ejbPassivate();
470                 }catch(Throwable JavaDoc e){
471                     try{
472                         Transaction JavaDoc tx = OpenEJB.getTransactionManager().getTransaction();
473                         if(tx!=null){
474                             tx.setRollbackOnly();
475                             throw new ApplicationException(new javax.transaction.TransactionRolledbackException JavaDoc("Reflection exception thrown while attempting to call ejbPassivate() on the instance. Exception message = "+e.getMessage()));
476                         }
477                     }catch(javax.transaction.SystemException JavaDoc se){
478                         logger.error("Transaction Manager getTransaction() failed.", se);
479                         throw new org.openejb.SystemException(se);
480                     }
481                     throw new ApplicationException(new java.rmi.RemoteException JavaDoc("Reflection exception thrown while attempting to call ejbPassivate() on the instance. Exception message = "+e.getMessage()));
482                 }finally{
483                     callContext.setCurrentOperation(currentOp);
484                 }
485             }
486             
487             /*
488             * The bean is returned to the method ready pool if its returned after servicing a find, ejbHome, business or create
489             * method and is not still part of a tx. While in the method ready pool the bean instance is not associated with a
490             * primary key and may be used to service a request for any bean of the same class.
491             */

492             Stack methodReadyPool = (Stack)poolMap.get(callContext.getDeploymentInfo().getDeploymentID());
493             methodReadyPool.push(bean);
494         }
495             
496         
497     }
498     
499     /**
500      * Should be called when an instance is simply removed from the pool
501      * Calls unsetEntityContext in the instance.
502      *
503      * @param callContext
504      * @param bean
505      * @exception org.openejb.SystemException
506      */

507     public void freeInstance(ThreadContext callContext, EntityBean JavaDoc bean)
508     throws org.openejb.SystemException{
509         
510         discardInstance(callContext, bean);
511
512         byte currentOp = callContext.getCurrentOperation();
513         callContext.setCurrentOperation(org.openejb.core.Operations.OP_UNSET_CONTEXT);
514
515         try{
516             /*
517             * unsetEntityContext executes in an unspecified transactional context. In this case we choose to
518             * allow it to have what every transaction context is current. Better then suspending it
519             * unnecessarily.
520             *
521             * We also chose not to invoke EntityContainer.invoke( ) method, which duplicate the exception handling
522             * logic but also attempt to manage the begining and end of a transaction. It its a container managed transaciton
523             * we don't want the TransactionScopeHandler commiting the transaction in afterInvoke() which is what it would attempt
524             * to do.
525             */

526             bean.unsetEntityContext();
527         }catch(java.lang.Exception JavaDoc e){
528             /*
529             * The EJB 1.1 specification does not specify how exceptions thrown by unsetEntityContext impact the
530             * transaction, if there is one. In this case we choose to do nothing since the instance is being disposed
531             * of anyway.
532             */

533             // TODO: Not spec compliant. Must fix. The spec clearly classifies this in the callback
534
// exception handling section 18.3.3 "Exceptions from container-invoked callbacks"
535
logger.info(getClass().getName()+".freeInstance: ignoring exception "+e+" on bean instance "+bean);
536         }finally{
537             callContext.setCurrentOperation(currentOp);
538         }
539         
540         
541     }
542     
543     /**
544      * An instance is freed if a EJBException or some other runtime exception occurs.
545      * To free an instance is to discard the instance so it can be garbage collected.
546      * If the instance is in the tx ready pool it is removed. EJB 1.1, section 12.3.2
547      * "Discard the instance (i.e. the Container must not invoke any business methods
548      * or container callbacks on the instance)."
549      *
550      * @param callContext
551      * @param bean
552      * @exception org.openejb.SystemException
553      */

554     public void discardInstance(ThreadContext callContext, EntityBean JavaDoc bean)
555     throws org.openejb.SystemException{
556         Transaction JavaDoc currentTx = null;
557         try{
558         currentTx = OpenEJB.getTransactionManager().getTransaction();
559         }catch(javax.transaction.SystemException JavaDoc se){
560             logger.error("Transaction Manager getTransaction() failed.", se);
561             throw new org.openejb.SystemException("TransactionManager failure");
562         }
563         if(currentTx != null){
564         if ( callContext.getPrimaryKey() == null )
565         return;
566             // a freed bean instance that is part of a tx must be removed from the
567
// tx ready pool and discarded.
568
Key key = new Key(currentTx, callContext.getDeploymentInfo().getDeploymentID(),callContext.getPrimaryKey());
569             
570             /*
571                The wrapper is removed (if pooled) so that it can not be accessed again. This is
572                especially important in the obtainInstance( ) method where a disassociated wrapper
573                in the txReadyPool is indicative of an entity bean that has been removed via
574                ejbRemove() rather than freed because of an error condition as is the case here.
575             */

576             SyncronizationWrapper wrapper = (SyncronizationWrapper)txReadyPool.remove(key);
577             
578             if(wrapper != null){
579                 /*
580                  It's not possible to deregister a wrapper with the transaction,
581                  but it can be removed from the tx pool and made inoperative by
582                  calling its disassociate method. The wrapper will be returned to the
583                  wrapper pool after the transaction completes
584                  (see SynchronizationWrapper.afterCompletion( ) method). The wrapper must
585                  be returned after the transaction completes so that it is not in the service
586                  of another bean when the TransactionManager calls its Synchronization methods.
587                  
588                  In addition, the bean instance is dereferenced so it can be garbage
589                  collected.
590                 */

591                 wrapper.disassociate();
592             }
593         }
594     }
595     
596     /*
597     * Instances of this class are used as keys for storing bean instances in the tx method
598     * ready pool. A compound key composed of the transaction, primary key, and deployment id
599     * identifiers is required to uniquely identify a bean in the tx method ready pool.
600     */

601     public static class Key {
602         private final Object JavaDoc deploymentID, primaryKey;
603         private final Transaction JavaDoc transaction;
604         
605         public Key(Transaction JavaDoc tx, Object JavaDoc depID, Object JavaDoc prKey){
606             if(tx==null || depID==null || prKey==null) {
607                 throw new IllegalArgumentException JavaDoc();
608             }
609             transaction = tx;
610             deploymentID = depID;
611             primaryKey = prKey;
612         }
613     public Object JavaDoc getPK()
614     {
615         return primaryKey;
616     }
617         public int hashCode( ){
618             return transaction.hashCode()^deploymentID.hashCode()^primaryKey.hashCode();
619         }
620         public boolean equals(Object JavaDoc other){
621             if(other != null && other.getClass() == EntityInstanceManager.Key.class){
622                 Key otherKey = (Key)other;
623                 if(otherKey.transaction.equals(transaction) && otherKey.deploymentID.equals(deploymentID) && otherKey.primaryKey.equals(primaryKey))
624                     return true;
625             }
626             return false;
627         }
628     }
629           
630     /*
631     * Instances of this class are used to wrap entity instances so that they can be registered
632     * with a tx. When the Synchronization.beforeCompletion is called, the bean's ejbStore method
633     * is invoked. When the Synchroniztion.afterCompletion is called, the bean instance is returned
634     * to the method ready pool. Instances of this class are not recycled anymore, because modern VMs
635     * (JDK1.3 and above) perform better for objects that are short lived.
636     */

637     protected class SyncronizationWrapper
638     implements javax.transaction.Synchronization JavaDoc{
639          private EntityBean JavaDoc bean;
640          /*
641          * <tt>isAvailable<tt> determines if the wrapper is still associated with a bean. If the bean identity is removed (ejbRemove)
642          * or if the bean instance is discarded, the wrapper will not longer be associated with a bean instances
643          * and therefore its beforeCompletion method will not process the ejbStore method.
644          */

645          private boolean isAvailable;
646          private boolean isAssociated;
647          private final ThreadContext context;
648          private final Key myIndex;
649          
650          public SyncronizationWrapper(EntityBean JavaDoc ebean, Key key, boolean available, ThreadContext ctx) throws OpenEJBException{
651              if(ebean==null || ctx==null || key==null) {
652                  throw new IllegalArgumentException JavaDoc();
653              }
654              bean = ebean;
655              isAvailable = available;
656              myIndex =key;
657              isAssociated=true;
658              try{
659                  context = (ThreadContext) ctx.clone();
660              }catch(CloneNotSupportedException JavaDoc e) {
661                  logger.error("Thread context class "+ctx.getClass()+" doesn't implement the Cloneable interface!", e);
662                  throw new OpenEJBException("Thread context class "+ctx.getClass()+" doesn't implement the Cloneable interface!");
663              }
664          }
665          public void disassociate( ){
666             isAssociated = false;
667          }
668          public boolean isAssociated(){
669             return isAssociated;
670          }
671          public synchronized boolean isAvailable(){
672             return isAvailable;
673          }
674          public void setEntityBean(EntityBean JavaDoc ebean){
675             isAvailable = true;
676             bean = ebean;
677          }
678          public EntityBean JavaDoc getEntityBean(){
679             isAvailable = false;
680             return bean;
681          }
682          public void beforeCompletion(){
683             if(isAssociated){
684                 // save current context, if any. The TM can call back with its own threads
685
// which won't have the ThreadContext set up properly.
686
ThreadContext currentContext = ThreadContext.getThreadContext();
687                 ThreadContext.setThreadContext(context);
688                 byte orginalOperation = context.getCurrentOperation();
689                 context.setCurrentOperation(org.openejb.core.Operations.OP_STORE);
690                 try{
691                     bean.ejbStore();
692                 }catch(Exception JavaDoc re){
693                     logger.error("Exception occured during ejbStore()", re);
694                     javax.transaction.TransactionManager JavaDoc txmgr = OpenEJB.getTransactionManager();
695                     try{
696                     txmgr.setRollbackOnly();
697                     }catch(javax.transaction.SystemException JavaDoc se){
698                         logger.error("Transaction manager reported error during setRollbackOnly()", se);
699                     }
700
701                 }finally {
702                     // restore old context, if any
703
ThreadContext.setThreadContext(currentContext);
704                 }
705             }
706          }
707          public void afterCompletion(int status){
708             txReadyPool.remove(myIndex);
709          }
710          
711     }
712 }
713
714
Popular Tags