001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.openejb.core.stateful;
017:
018: import org.apache.openejb.ApplicationException;
019: import org.apache.openejb.Injection;
020: import org.apache.openejb.InvalidateReferenceException;
021: import org.apache.openejb.OpenEJBException;
022: import org.apache.openejb.SystemException;
023: import org.apache.openejb.core.BaseContext;
024: import org.apache.openejb.core.CoreDeploymentInfo;
025: import org.apache.openejb.core.CoreUserTransaction;
026: import org.apache.openejb.core.Operation;
027: import org.apache.openejb.core.ThreadContext;
028: import org.apache.openejb.core.interceptor.InterceptorData;
029: import org.apache.openejb.core.interceptor.InterceptorStack;
030: import org.apache.openejb.core.ivm.IntraVmCopyMonitor;
031: import org.apache.openejb.core.transaction.TransactionRolledbackException;
032: import org.apache.openejb.persistence.JtaEntityManagerRegistry;
033: import org.apache.openejb.spi.SecurityService;
034: import org.apache.openejb.util.Index;
035: import org.apache.openejb.util.LogCategory;
036: import org.apache.openejb.util.Logger;
037: import org.apache.openejb.util.PojoSerialization;
038: import org.apache.xbean.recipe.ObjectRecipe;
039: import org.apache.xbean.recipe.Option;
040: import org.apache.xbean.recipe.StaticRecipe;
041: import org.apache.xbean.recipe.ConstructionException;
042:
043: import javax.ejb.EJBException;
044: import javax.ejb.SessionContext;
045: import javax.ejb.SessionBean;
046: import javax.naming.Context;
047: import javax.naming.NamingException;
048: import javax.persistence.EntityManager;
049: import javax.persistence.EntityManagerFactory;
050: import javax.transaction.Transaction;
051: import javax.transaction.TransactionManager;
052: import java.lang.reflect.Method;
053: import java.rmi.NoSuchObjectException;
054: import java.rmi.RemoteException;
055: import java.util.Hashtable;
056: import java.util.LinkedList;
057: import java.util.Map;
058: import java.util.HashMap;
059: import java.util.List;
060: import java.io.Serializable;
061: import java.io.ObjectStreamException;
062:
063: public class StatefulInstanceManager {
064: public static final Logger logger = Logger.getInstance(
065: LogCategory.OPENEJB, "org.apache.openejb.util.resources");
066:
067: protected final long timeOut;
068:
069: // queue of beans for LRU algorithm
070: private final BeanEntryQueue lruQueue;
071:
072: private final PassivationStrategy passivator;
073:
074: private final int bulkPassivationSize;
075:
076: private final TransactionManager transactionManager;
077: private final SecurityService securityService;
078: private final JtaEntityManagerRegistry jtaEntityManagerRegistry;
079:
080: public StatefulInstanceManager(
081: TransactionManager transactionManager,
082: SecurityService securityService,
083: JtaEntityManagerRegistry jtaEntityManagerRegistry,
084: Class passivatorClass, int timeout, int poolSize,
085: int bulkPassivate) throws OpenEJBException {
086: this .transactionManager = transactionManager;
087: this .securityService = securityService;
088: this .jtaEntityManagerRegistry = jtaEntityManagerRegistry;
089: this .lruQueue = new BeanEntryQueue(poolSize);
090: if (poolSize == 0) {
091: this .bulkPassivationSize = 1;
092: } else {
093: this .bulkPassivationSize = Math
094: .min(bulkPassivate, poolSize);
095: }
096: this .timeOut = timeout * 60 * 1000;
097:
098: try {
099: passivatorClass = (passivatorClass == null) ? SimplePassivater.class
100: : passivatorClass;
101: passivator = (PassivationStrategy) passivatorClass
102: .newInstance();
103: } catch (Exception e) {
104: throw new OpenEJBException(
105: "Could not create the passivator "
106: + passivatorClass.getName(), e);
107: }
108: }
109:
110: public void deploy(CoreDeploymentInfo deploymentInfo,
111: Index<Method, StatefulContainer.MethodType> index)
112: throws OpenEJBException {
113: deploymentInfo.setContainerData(new Data(index));
114: }
115:
116: public void undeploy(CoreDeploymentInfo deploymentInfo)
117: throws OpenEJBException {
118: Data data = (Data) deploymentInfo.getContainerData();
119: if (data != null) {
120: for (BeanEntry entry : data.getBeanIndex().values()) {
121: lruQueue.remove(entry);
122: }
123: deploymentInfo.setContainerData(null);
124: }
125: }
126:
127: Index<Method, StatefulContainer.MethodType> getMethodIndex(
128: CoreDeploymentInfo deploymentInfo) {
129: Data data = (Data) deploymentInfo.getContainerData();
130: return data.getMethodIndex();
131: }
132:
133: public Transaction getBeanTransaction(ThreadContext callContext)
134: throws OpenEJBException {
135: BeanEntry entry = getBeanEntry(callContext);
136: if (entry == null)
137: return null;
138: return entry.beanTransaction;
139: }
140:
141: public void setBeanTransaction(ThreadContext callContext,
142: Transaction beanTransaction) throws OpenEJBException {
143: BeanEntry entry = getBeanEntry(callContext);
144: entry.beanTransaction = beanTransaction;
145: }
146:
147: public Map<EntityManagerFactory, EntityManager> getEntityManagers(
148: ThreadContext callContext,
149: Index<EntityManagerFactory, Map> factories)
150: throws OpenEJBException {
151: BeanEntry entry = getBeanEntry(callContext);
152: return entry.getEntityManagers(factories);
153: }
154:
155: public void setEntityManagers(ThreadContext callContext,
156: Index<EntityManagerFactory, EntityManager> entityManagers)
157: throws OpenEJBException {
158: BeanEntry entry = getBeanEntry(callContext);
159: entry.setEntityManagers(entityManagers);
160: }
161:
162: public Object newInstance(Object primaryKey, Class beanClass)
163: throws OpenEJBException {
164: Object bean = null;
165:
166: ThreadContext threadContext = ThreadContext.getThreadContext();
167: Operation currentOperation = threadContext
168: .getCurrentOperation();
169: try {
170: ObjectRecipe objectRecipe = new ObjectRecipe(beanClass);
171: objectRecipe.allow(Option.FIELD_INJECTION);
172: objectRecipe.allow(Option.PRIVATE_PROPERTIES);
173: // objectRecipe.allow(Option.IGNORE_MISSING_PROPERTIES);
174:
175: ThreadContext callContext = ThreadContext
176: .getThreadContext();
177: CoreDeploymentInfo deploymentInfo = callContext
178: .getDeploymentInfo();
179: Context ctx = deploymentInfo.getJndiEnc();
180: SessionContext sessionContext;
181: synchronized (this ) {
182: try {
183: sessionContext = (SessionContext) ctx
184: .lookup("java:comp/EJBContext");
185: } catch (NamingException e1) {
186: sessionContext = createSessionContext();
187: ctx.bind("java:comp/EJBContext", sessionContext);
188: }
189: }
190: if (javax.ejb.SessionBean.class.isAssignableFrom(beanClass)
191: || hasSetSessionContext(beanClass)) {
192: callContext.setCurrentOperation(Operation.INJECTION);
193: objectRecipe.setProperty("sessionContext",
194: new StaticRecipe(sessionContext));
195: }
196:
197: fillInjectionProperties(objectRecipe, beanClass,
198: deploymentInfo, ctx);
199:
200: bean = objectRecipe.create(beanClass.getClassLoader());
201:
202: Map unsetProperties = objectRecipe.getUnsetProperties();
203: if (unsetProperties.size() > 0) {
204: for (Object property : unsetProperties.keySet()) {
205: logger.warning("Injection: No such property '"
206: + property + "' in class "
207: + beanClass.getName());
208: }
209: }
210: HashMap<String, Object> interceptorInstances = new HashMap<String, Object>();
211: for (InterceptorData interceptorData : deploymentInfo
212: .getAllInterceptors()) {
213: if (interceptorData.getInterceptorClass().equals(
214: beanClass))
215: continue;
216:
217: Class clazz = interceptorData.getInterceptorClass();
218: ObjectRecipe interceptorRecipe = new ObjectRecipe(clazz);
219: interceptorRecipe.allow(Option.FIELD_INJECTION);
220: interceptorRecipe.allow(Option.PRIVATE_PROPERTIES);
221: interceptorRecipe
222: .allow(Option.IGNORE_MISSING_PROPERTIES);
223:
224: fillInjectionProperties(interceptorRecipe, clazz,
225: deploymentInfo, ctx);
226:
227: try {
228: Object interceptorInstance = interceptorRecipe
229: .create(clazz.getClassLoader());
230: interceptorInstances.put(clazz.getName(),
231: interceptorInstance);
232: } catch (ConstructionException e) {
233: throw new Exception(
234: "Failed to create interceptor: "
235: + clazz.getName(), e);
236: }
237: }
238:
239: interceptorInstances.put(beanClass.getName(), bean);
240:
241: callContext.setCurrentOperation(Operation.POST_CONSTRUCT);
242:
243: List<InterceptorData> callbackInterceptors = deploymentInfo
244: .getCallbackInterceptors();
245: InterceptorStack interceptorStack = new InterceptorStack(
246: bean, null, Operation.POST_CONSTRUCT,
247: callbackInterceptors, interceptorInstances);
248: interceptorStack.invoke();
249:
250: bean = newInstance(primaryKey, bean, interceptorInstances);
251:
252: } catch (Throwable callbackException) {
253: /*
254: In the event of an exception, OpenEJB is required to log the exception, evict the instance,
255: and mark the transaction for rollback. If there is a transaction to rollback, then the a
256: javax.transaction.TransactionRolledbackException must be throw to the client. Otherwise a
257: java.rmi.RemoteException is thrown to the client.
258: See EJB 1.1 specification, section 12.3.2
259: See EJB 2.0 specification, section 18.3.3
260: */
261: handleCallbackException(callbackException, bean,
262: threadContext, "setSessionContext");
263: } finally {
264: threadContext.setCurrentOperation(currentOperation);
265: }
266:
267: // add to index
268: BeanEntry entry = newBeanEntry(primaryKey, bean);
269: getBeanIndex(threadContext).put(primaryKey, entry);
270:
271: return bean;
272: }
273:
274: protected Instance newInstance(Object primaryKey, Object bean,
275: Map<String, Object> interceptorInstances) {
276: return new Instance(bean, interceptorInstances);
277: }
278:
279: protected BeanEntry newBeanEntry(Object primaryKey, Object bean) {
280: return new BeanEntry(bean, primaryKey, timeOut);
281: }
282:
283: private static void fillInjectionProperties(
284: ObjectRecipe objectRecipe, Class clazz,
285: CoreDeploymentInfo deploymentInfo, Context context) {
286: for (Injection injection : deploymentInfo.getInjections()) {
287: if (!injection.getTarget().isAssignableFrom(clazz))
288: continue;
289: try {
290: String jndiName = injection.getJndiName();
291: Object object = context.lookup("java:comp/env/"
292: + jndiName);
293: if (object instanceof String) {
294: String string = (String) object;
295: // Pass it in raw so it could be potentially converted to
296: // another data type by an xbean-reflect property editor
297: objectRecipe.setProperty(injection.getTarget()
298: .getName()
299: + "/" + injection.getName(), string);
300: } else {
301: objectRecipe.setProperty(injection.getTarget()
302: .getName()
303: + "/" + injection.getName(),
304: new StaticRecipe(object));
305: }
306: } catch (NamingException e) {
307: logger
308: .warning("Injection data not found in enc: jndiName='"
309: + injection.getJndiName()
310: + "', target="
311: + injection.getTarget()
312: + "/" + injection.getName());
313: }
314: }
315: }
316:
317: private boolean hasSetSessionContext(Class beanClass) {
318: try {
319: beanClass.getMethod("setSessionContext",
320: SessionContext.class);
321: return true;
322: } catch (NoSuchMethodException e) {
323: return false;
324: }
325: }
326:
327: private SessionContext createSessionContext() {
328: StatefulUserTransaction userTransaction = new StatefulUserTransaction(
329: new CoreUserTransaction(transactionManager),
330: jtaEntityManagerRegistry);
331: return new StatefulContext(transactionManager, securityService,
332: userTransaction);
333: }
334:
335: public Object obtainInstance(Object primaryKey,
336: ThreadContext callContext) throws OpenEJBException {
337: if (primaryKey == null) {
338: throw new SystemException(
339: new NullPointerException(
340: "Cannot obtain an instance of the stateful session bean with a null session id"));
341: }
342:
343: // look for entry in index
344: BeanEntry entry = getBeanIndex(callContext).get(primaryKey);
345:
346: // if we didn't find the bean in the index, try to activate it
347: if (entry == null) {
348: Object bean = activateInstance(primaryKey, callContext);
349: return bean;
350: }
351:
352: // if the bean is already in a transaction, just return it
353: if (entry.beanTransaction != null) {
354: return entry.bean;
355: }
356:
357: // remove from the queue so it is not passivated while in use
358: BeanEntry queueEntry = lruQueue.remove(entry);
359: if (queueEntry != null) {
360: // if bean is timed out, destroy it
361: if (entry.isTimedOut()) {
362: entry = getBeanIndex(callContext).remove(
363: entry.primaryKey);
364: handleTimeout(entry, callContext);
365: throw new InvalidateReferenceException(
366: new NoSuchObjectException(
367: "Stateful SessionBean has timed-out"));
368: }
369: return entry.bean;
370: } else {
371: // if it is not in the queue, the bean is already being invoked
372: // the only reentrant/concurrent operations allowed are Session synchronization callbacks
373: Operation currentOperation = callContext
374: .getCurrentOperation();
375: if (currentOperation != Operation.AFTER_COMPLETION
376: && currentOperation != Operation.BEFORE_COMPLETION) {
377: throw new ApplicationException(new RemoteException(
378: "Concurrent calls not allowed"));
379: }
380:
381: return entry.bean;
382: }
383: }
384:
385: private Object activateInstance(Object primaryKey,
386: ThreadContext callContext) throws SystemException,
387: ApplicationException {
388: // attempt to active a passivated entity
389: BeanEntry entry = activate(primaryKey);
390: if (entry == null) {
391: throw new InvalidateReferenceException(
392: new NoSuchObjectException("Not Found"));
393: }
394:
395: return activateInstance(callContext, entry);
396: }
397:
398: public Object activateInstance(ThreadContext callContext,
399: BeanEntry entry) throws SystemException,
400: ApplicationException {
401: if (entry.isTimedOut()) {
402: // Since the bean instance hasn't had its ejbActivate() method called yet,
403: // it is still considered to be passivated at this point. Instances that timeout
404: // while passivated must be evicted WITHOUT having their ejbRemove()
405: // method invoked. Section 6.6 of EJB 1.1 specification.
406: throw new InvalidateReferenceException(
407: new NoSuchObjectException("Timed Out"));
408: }
409:
410: // call the activate method
411: Operation currentOperation = callContext.getCurrentOperation();
412: callContext.setCurrentOperation(Operation.ACTIVATE);
413: try {
414: CoreDeploymentInfo deploymentInfo = callContext
415: .getDeploymentInfo();
416:
417: StatefulInstanceManager.Instance instance = (StatefulInstanceManager.Instance) entry.bean;
418: Method remove = instance.bean instanceof SessionBean ? SessionBean.class
419: .getMethod("ejbActivate")
420: : null;
421:
422: List<InterceptorData> callbackInterceptors = deploymentInfo
423: .getCallbackInterceptors();
424: InterceptorStack interceptorStack = new InterceptorStack(
425: instance.bean, remove, Operation.ACTIVATE,
426: callbackInterceptors, instance.interceptors);
427:
428: interceptorStack.invoke();
429: } catch (Throwable callbackException) {
430: /*
431: In the event of an exception, OpenEJB is required to log the exception, evict the instance,
432: and mark the transaction for rollback. If there is a transaction to rollback, then the a
433: javax.transaction.TransactionRolledbackException must be throw to the client. Otherwise a
434: java.rmi.RemoteException is thrown to the client.
435: See EJB 1.1 specification, section 12.3.2
436: */
437: handleCallbackException(callbackException, entry.bean,
438: callContext, "ejbActivate");
439: } finally {
440: callContext.setCurrentOperation(currentOperation);
441: }
442:
443: // add it to the index
444: getBeanIndex(callContext).put(entry.primaryKey, entry);
445:
446: return entry.bean;
447: }
448:
449: protected void handleTimeout(BeanEntry entry,
450: ThreadContext threadContext) {
451: Operation currentOperation = threadContext
452: .getCurrentOperation();
453: threadContext.setCurrentOperation(Operation.PRE_DESTROY);
454: BaseContext.State[] originalAllowedStates = threadContext
455: .setCurrentAllowedStates(StatefulContext.getStates());
456: CoreDeploymentInfo deploymentInfo = threadContext
457: .getDeploymentInfo();
458: Instance instance = (Instance) entry.bean;
459:
460: try {
461: Method remove = instance.bean instanceof SessionBean ? SessionBean.class
462: .getMethod("ejbRemove")
463: : null;
464:
465: List<InterceptorData> callbackInterceptors = deploymentInfo
466: .getCallbackInterceptors();
467: InterceptorStack interceptorStack = new InterceptorStack(
468: instance.bean, remove, Operation.PRE_DESTROY,
469: callbackInterceptors, instance.interceptors);
470:
471: interceptorStack.invoke();
472: } catch (Throwable callbackException) {
473: /*
474: Exceptions are processed "quietly"; they are not reported to the client since
475: the timeout that caused the ejbRemove() operation did not, "technically", take
476: place in the context of a client call. Logically, it may have timeout sometime
477: before the client call.
478: */
479: String logMessage = "An unexpected exception occured while invoking the ejbRemove method on the timed-out Stateful SessionBean instance; "
480: + callbackException.getClass().getName()
481: + " "
482: + callbackException.getMessage();
483:
484: /* [1] Log the exception or error */
485: logger.error(logMessage);
486:
487: } finally {
488: logger
489: .info("Removing the timed-out stateful session bean instance "
490: + entry.primaryKey);
491: threadContext.setCurrentOperation(currentOperation);
492: threadContext
493: .setCurrentAllowedStates(originalAllowedStates);
494: }
495: }
496:
497: public void poolInstance(ThreadContext callContext, Object bean)
498: throws OpenEJBException {
499: // Don't pool if the bean has been undeployed
500: if (callContext.getDeploymentInfo().isDestroyed())
501: return;
502:
503: Object primaryKey = callContext.getPrimaryKey();
504: if (primaryKey == null || bean == null) {
505: throw new SystemException("Invalid arguments");
506: }
507:
508: BeanEntry entry = getBeanIndex(callContext).get(primaryKey);
509: if (entry == null) {
510: entry = activate(primaryKey);
511: if (entry == null) {
512: throw new SystemException("Invalid primaryKey:"
513: + primaryKey);
514: }
515: } else if (entry.bean != bean) {
516: throw new SystemException("Invalid ID for bean");
517: }
518:
519: if (entry.beanTransaction == null) {
520: if (callContext.getCurrentOperation() != Operation.CREATE) {
521: try {
522: entry.beanTransaction = transactionManager
523: .getTransaction();
524: } catch (javax.transaction.SystemException se) {
525: throw new SystemException(
526: "TransactionManager failure", se);
527: }
528: }
529:
530: // only put in LRU if no current transaction
531: if (entry.beanTransaction == null) {
532: // add it to end of Queue; the most reciently used bean
533: lruQueue.add(entry);
534:
535: onPoolInstanceWithoutTransaction(callContext, entry);
536: }
537: }
538: }
539:
540: protected void onPoolInstanceWithoutTransaction(
541: ThreadContext callContext, BeanEntry entry) {
542: }
543:
544: public Object freeInstance(ThreadContext threadContext)
545: throws SystemException {
546: Object primaryKey = threadContext.getPrimaryKey();
547: BeanEntry entry = getBeanIndex(threadContext)
548: .remove(primaryKey);// remove frm index
549: if (entry == null) {
550: entry = activate(primaryKey);
551: } else {
552: lruQueue.remove(entry);
553: }
554:
555: if (entry == null) {
556: return null;
557: }
558:
559: onFreeBeanEntry(threadContext, entry);
560:
561: return entry.bean;
562: }
563:
564: protected void onFreeBeanEntry(ThreadContext threadContext,
565: BeanEntry entry) {
566: }
567:
568: protected void passivate() throws SystemException {
569: final ThreadContext threadContext = ThreadContext
570: .getThreadContext();
571: Hashtable<Object, BeanEntry> stateTable = new Hashtable<Object, BeanEntry>(
572: bulkPassivationSize);
573:
574: BeanEntry currentEntry;
575: final Operation currentOperation = threadContext
576: .getCurrentOperation();
577: final BaseContext.State[] originalAllowedStates = threadContext
578: .setCurrentAllowedStates(StatefulContext.getStates());
579: CoreDeploymentInfo deploymentInfo = threadContext
580: .getDeploymentInfo();
581: try {
582: for (int i = 0; i < bulkPassivationSize; ++i) {
583: currentEntry = lruQueue.first();
584: if (currentEntry == null) {
585: break;
586: }
587: getBeanIndex(threadContext).remove(
588: currentEntry.primaryKey);
589: if (currentEntry.isTimedOut()) {
590: handleTimeout(currentEntry, threadContext);
591: } else {
592: passivate(threadContext, currentEntry);
593: stateTable.put(currentEntry.primaryKey,
594: currentEntry);
595: }
596: }
597: } finally {
598: threadContext.setCurrentOperation(currentOperation);
599: threadContext
600: .setCurrentAllowedStates(originalAllowedStates);
601: }
602:
603: /*
604: the IntraVmCopyMonitor.prePssivationOperation() demarcates
605: the begining of passivation; used by EjbHomeProxyHandler,
606: EjbObjectProxyHandler, IntraVmMetaData, and IntraVmHandle
607: to deterime how serialization for these artifacts.
608: */
609: try {
610: IntraVmCopyMonitor.prePassivationOperation();
611:
612: passivator.passivate(stateTable);
613: } finally {
614:
615: IntraVmCopyMonitor.postPassivationOperation();
616: }
617: }
618:
619: public void passivate(ThreadContext threadContext,
620: BeanEntry currentEntry) {
621: CoreDeploymentInfo deploymentInfo = threadContext
622: .getDeploymentInfo();
623: threadContext.setCurrentOperation(Operation.PASSIVATE);
624: try {
625: StatefulInstanceManager.Instance instance = (StatefulInstanceManager.Instance) currentEntry.bean;
626:
627: Method passivate = instance.bean instanceof SessionBean ? SessionBean.class
628: .getMethod("ejbPassivate")
629: : null;
630:
631: List<InterceptorData> callbackInterceptors = deploymentInfo
632: .getCallbackInterceptors();
633: InterceptorStack interceptorStack = new InterceptorStack(
634: instance.bean, passivate, Operation.PASSIVATE,
635: callbackInterceptors, instance.interceptors);
636:
637: interceptorStack.invoke();
638:
639: } catch (Throwable e) {
640:
641: String logMessage = "An unexpected exception occured while invoking the ejbPassivate method on the Stateful SessionBean instance; "
642: + e.getClass().getName() + " " + e.getMessage();
643:
644: /* [1] Log the exception or error */
645: logger.error(logMessage);
646: }
647: }
648:
649: protected BeanEntry activate(Object primaryKey)
650: throws SystemException {
651: return (BeanEntry) passivator.activate(primaryKey);
652: }
653:
654: protected InvalidateReferenceException destroy(
655: ThreadContext callContext, BeanEntry entry, Exception t)
656: throws SystemException {
657:
658: getBeanIndex(callContext).remove(entry.primaryKey);// remove frm index
659: lruQueue.remove(entry);// remove from queue
660: if (entry.beanTransaction != null) {
661: try {
662: entry.beanTransaction.setRollbackOnly();
663: } catch (javax.transaction.SystemException se) {
664: throw new SystemException(se);
665: } catch (IllegalStateException ise) {
666: throw new SystemException(
667: "Attempt to rollback a non-tx context", ise);
668: } catch (SecurityException lse) {
669: throw new SystemException(
670: "Container not authorized to rollback tx", lse);
671: }
672: return new InvalidateReferenceException(
673: new TransactionRolledbackException(t));
674: } else if (t instanceof RemoteException) {
675: return new InvalidateReferenceException(t);
676: } else {
677: EJBException e = (EJBException) t;
678: return new InvalidateReferenceException(
679: new RemoteException(e.getMessage(), e
680: .getCausedByException()));
681: }
682:
683: }
684:
685: protected BeanEntry getBeanEntry(ThreadContext callContext)
686: throws OpenEJBException {
687: Object primaryKey = callContext.getPrimaryKey();
688: if (primaryKey == null) {
689: throw new SystemException(
690: new NullPointerException(
691: "The primary key is null. Cannot get the bean entry"));
692: }
693: BeanEntry entry = getBeanIndex(callContext).get(primaryKey);
694: if (entry == null) {
695: Object bean = this .obtainInstance(primaryKey, ThreadContext
696: .getThreadContext());
697: this .poolInstance(callContext, bean);
698: entry = getBeanIndex(callContext).get(primaryKey);
699: }
700: return entry;
701: }
702:
703: protected Hashtable<Object, BeanEntry> getBeanIndex(
704: ThreadContext threadContext) {
705: CoreDeploymentInfo deployment = threadContext
706: .getDeploymentInfo();
707: Data data = (Data) deployment.getContainerData();
708: return data.getBeanIndex();
709: }
710:
711: private static class Data {
712: private final Index<Method, StatefulContainer.MethodType> methodIndex;
713: private final Hashtable<Object, BeanEntry> beanIndex = new Hashtable<Object, BeanEntry>();
714:
715: private Data(
716: Index<Method, StatefulContainer.MethodType> methodIndex) {
717: this .methodIndex = methodIndex;
718: }
719:
720: public Index<Method, StatefulContainer.MethodType> getMethodIndex() {
721: return methodIndex;
722: }
723:
724: public Hashtable<Object, BeanEntry> getBeanIndex() {
725: return beanIndex;
726: }
727: }
728:
729: class BeanEntryQueue {
730: private final LinkedList<BeanEntry> list;
731: private final int capacity;
732:
733: protected BeanEntryQueue(int preferedCapacity) {
734: capacity = preferedCapacity;
735: list = new LinkedList<BeanEntry>();
736: }
737:
738: protected synchronized BeanEntry first() {
739: return list.removeFirst();
740: }
741:
742: protected synchronized void add(BeanEntry entry)
743: throws SystemException {
744: entry.resetTimeOut();
745:
746: list.addLast(entry);
747: entry.inQueue = true;
748:
749: if (list.size() >= capacity) {// is the LRU QUE full?
750: passivate();
751: }
752: }
753:
754: protected synchronized BeanEntry remove(BeanEntry entry) {
755: if (!entry.inQueue) {
756: return null;
757: }
758: if (list.remove(entry)) {
759: entry.inQueue = false;
760: return entry;
761: } else {
762:
763: return null;
764: }
765: }
766: }
767:
768: protected void handleCallbackException(Throwable e,
769: Object instance, ThreadContext callContext, String callBack)
770: throws ApplicationException, SystemException {
771:
772: String remoteMessage = "An unexpected exception occured while invoking the "
773: + callBack
774: + " method on the Stateful SessionBean instance";
775: String logMessage = remoteMessage + "; "
776: + e.getClass().getName() + " " + e.getMessage();
777:
778: /* [1] Log the exception or error */
779: logger.error(logMessage);
780:
781: /* [2] If the instance is in a transaction, mark the transaction for rollback. */
782: Transaction transaction = null;
783: try {
784: transaction = transactionManager.getTransaction();
785: } catch (Throwable t) {
786: logger
787: .error("Could not retreive the current transaction from the transaction manager while handling a callback exception from the "
788: + callBack
789: + " method of bean "
790: + callContext.getPrimaryKey());
791: }
792: if (transaction != null) {
793: markTxRollbackOnly(transaction);
794: }
795:
796: /* [3] Discard the instance */
797: freeInstance(callContext);
798:
799: /* [4] throw the java.rmi.RemoteException to the client */
800: if (transaction == null) {
801: throw new InvalidateReferenceException(new RemoteException(
802: remoteMessage, e));
803: } else {
804: throw new InvalidateReferenceException(
805: new TransactionRolledbackException(remoteMessage, e));
806: }
807:
808: }
809:
810: protected void markTxRollbackOnly(Transaction tx)
811: throws SystemException {
812: try {
813: if (tx != null) {
814: tx.setRollbackOnly();
815: }
816: } catch (javax.transaction.SystemException se) {
817: throw new SystemException(se);
818: }
819: }
820:
821: public static class Instance implements Serializable {
822: public final Object bean;
823: public final Map<String, Object> interceptors;
824:
825: public Instance(Object bean, Map<String, Object> interceptors) {
826: this .bean = bean;
827: this .interceptors = interceptors;
828: }
829:
830: protected Object writeReplace() throws ObjectStreamException {
831: return new Serialization(this );
832: }
833:
834: private static class Serialization implements Serializable {
835: public final Object bean;
836: public final Map<String, Object> interceptors;
837:
838: public Serialization(Instance i) {
839: if (i.bean instanceof Serializable) {
840: bean = i.bean;
841: } else {
842: bean = new PojoSerialization(i.bean);
843: }
844:
845: interceptors = new HashMap(i.interceptors.size());
846: for (Map.Entry<String, Object> e : i.interceptors
847: .entrySet()) {
848: if (e.getValue() == i.bean) {
849: // need to use the same wrapped reference or well get two copies.
850: interceptors.put(e.getKey(), bean);
851: } else if (!(e.getValue() instanceof Serializable)) {
852: interceptors.put(e.getKey(),
853: new PojoSerialization(e.getValue()));
854: }
855: }
856: }
857:
858: protected Object readResolve() throws ObjectStreamException {
859: // Anything wrapped with PojoSerialization will have been automatically
860: // unwrapped via it's own readResolve so passing in the raw bean
861: // and interceptors variables is totally fine.
862: return new Instance(bean, interceptors);
863: }
864: }
865: }
866: }
|