001: /**
002: * JOnAS: Java(TM) Open Application Server
003: * Copyright (C) 1999 Bull S.A.
004: * Contact: jonas-team@objectweb.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: JEntityContext.java 10098 2007-03-26 15:41:28Z durieuxp $
023: * --------------------------------------------------------------------------
024: */package org.objectweb.jonas_ejb.container;
025:
026: import java.rmi.RemoteException;
027:
028: import javax.ejb.EJBException;
029: import javax.ejb.EJBLocalObject;
030: import javax.ejb.EJBObject;
031: import javax.ejb.EntityBean;
032: import javax.ejb.EntityContext;
033: import javax.ejb.NoSuchObjectLocalException;
034: import javax.ejb.RemoveException;
035: import javax.ejb.TimerService;
036: import javax.transaction.Status;
037: import javax.transaction.Synchronization;
038: import javax.transaction.SystemException;
039: import javax.transaction.Transaction;
040:
041: import org.objectweb.jonas_ejb.deployment.api.EntityDesc;
042:
043: import org.objectweb.util.monolog.api.BasicLevel;
044:
045: /**
046: * This class implements javax.ejb.EntityContext interface. An Entitycontext is
047: * bound to a bean instance. To be used, it must be associated to a
048: * JEntitySwitch, and possibly to a Transaction. In case the Context is used
049: * inside a Transaction, we use the Synchronization interface to be aware of
050: * transaction demarcations.
051: * @author Philippe Coq, Philippe Durieux
052: */
053: public class JEntityContext extends JContext implements EntityContext,
054: Synchronization {
055:
056: /**
057: * this instance has been modified
058: */
059: private boolean dirty = false;
060:
061: /**
062: * This Context has been initialized, i.e. the EntitySwitch is
063: * relevant (initEntityContext has been called)
064: */
065: private boolean initialized = false;
066:
067: /**
068: * Transaction related to this synchronization
069: */
070: Transaction beanCoord = null;
071:
072: /**
073: * @return the transaction associated to this context.
074: */
075: public Transaction getMyTx() {
076: return beanCoord;
077: }
078:
079: private boolean mustnotifywriting = false;
080:
081: private JEntitySwitch bs = null;
082:
083: /**
084: * true between a remove and the commit
085: */
086: boolean ismarkedremoved;
087:
088: /**
089: * true if just created in this transaction
090: */
091: boolean isnewinstance = false;
092:
093: // ------------------------------------------------------------------
094: // constructors
095: // ------------------------------------------------------------------
096:
097: /**
098: * Constructs an EntityContext the Context has to be initialized after this.
099: * @param bf - the JEntityFactory
100: * @param eb - the Enterprise Bean instance
101: */
102: public JEntityContext(JEntityFactory bf, EntityBean eb) {
103: super (bf, eb);
104: }
105:
106: // ------------------------------------------------------------------
107: // EJBContext implementation
108: // ------------------------------------------------------------------
109:
110: /**
111: * Get access to the EJB Timer Service.
112: * @return the EJB Timer Service
113: * @throws IllegalStateException Thrown if the instance is not allowed to
114: * use this method
115: */
116: public TimerService getTimerService() throws IllegalStateException {
117: int mystate = getState();
118: if (TraceEjb.isDebugIc()) {
119: TraceEjb.interp.log(BasicLevel.DEBUG, "" + mystate);
120: }
121: switch (mystate) {
122: case 0:
123: TraceEjb.logger.log(BasicLevel.ERROR, "not allowed here");
124: throw new IllegalStateException(
125: "getTimerService not allowed here");
126: case 4:
127: TraceEjb.logger.log(BasicLevel.ERROR, "not allowed here");
128: throw new IllegalStateException(
129: "getTimerService not allowed here");
130: case 1:
131: // should work when called from ejbCreate,
132: // but timer operations should not -> return a dummy
133: // TimerService.
134: return bf.getTimerService();
135: default:
136: return bs == null ? bf.getTimerService() : bs
137: .getEntityTimerService();
138: }
139: }
140:
141: // ------------------------------------------------------------------
142: // EntityContext implementation
143: // ------------------------------------------------------------------
144:
145: /**
146: * Obtains a reference to the EJB object that is currently associated with
147: * the instance.
148: * @return The EJB object currently associated with the instance.
149: * @throws IllegalStateException Thrown if the instance invokes this method
150: * while the instance is in a state that does not allow the instance
151: * to invoke this method.
152: */
153: public EJBObject getEJBObject() throws IllegalStateException {
154: if (TraceEjb.isDebugIc()) {
155: TraceEjb.interp.log(BasicLevel.DEBUG, "");
156: }
157: if (ismarkedremoved) {
158: TraceEjb.logger.log(BasicLevel.ERROR, "marked removed");
159: throw new IllegalStateException("EJB is removed");
160: }
161: if (bs == null) {
162: TraceEjb.logger.log(BasicLevel.ERROR,
163: "no EntitySwitch for " + this );
164: throw new IllegalStateException("no EntitySwitch");
165: }
166: EJBObject ejbobject = bs.getRemote();
167: if (ejbobject == null) {
168: throw new IllegalStateException("No Remote Interface for "
169: + this );
170: }
171: return ejbobject;
172: }
173:
174: /**
175: * Obtain a reference to the EJB local object that is currently associated
176: * with the instance.
177: * @param check if the local interface has been defined
178: * @return The EJB local object currently associated with the instance.
179: * @throws IllegalStateException - if the instance invokes this method while
180: * the instance is in a state that does not allow the instance to
181: * invoke this method, or if the instance does not have a local
182: * interface.
183: */
184: private EJBLocalObject getEJBLocalObject(boolean check)
185: throws IllegalStateException {
186: if (TraceEjb.isDebugIc()) {
187: TraceEjb.interp.log(BasicLevel.DEBUG, "");
188: }
189: if (check) {
190: if (!bf.dd.hasDefinedLocalInterface()) {
191: TraceEjb.logger.log(BasicLevel.ERROR,
192: "No Local Interface declared for this bean");
193: throw new IllegalStateException(
194: "No Local Interface declared for this bean");
195: }
196: }
197: if (ismarkedremoved) {
198: TraceEjb.logger.log(BasicLevel.ERROR, "marked removed: "
199: + this );
200: throw new IllegalStateException("EJB is removed");
201: }
202: if (bs == null) {
203: TraceEjb.logger.log(BasicLevel.ERROR,
204: "no EntitySwitch for " + this );
205: throw new IllegalStateException("no EntitySwitch");
206: }
207: EJBLocalObject ejblocalobject = bs.getLocal();
208: if (ejblocalobject == null) {
209: throw new IllegalStateException("No Local Object for "
210: + this );
211: }
212: return ejblocalobject;
213: }
214:
215: /**
216: * Obtain a reference to the EJB local object that is currently associated
217: * with the instance.
218: * @return The EJB local object currently associated with the instance.
219: * @throws IllegalStateException - if the instance invokes this method while
220: * the instance is in a state that does not allow the instance to
221: * invoke this method, or if the instance does not have a local
222: * interface.
223: */
224: public EJBLocalObject getEJBLocalObject()
225: throws IllegalStateException {
226: return getEJBLocalObject(true);
227: }
228:
229: /**
230: * Obtain a reference to the EJB local object that is currently associated
231: * with the instance.
232: * (internal use of getEJBLocalObject with no check).
233: * @return The EJB local object currently associated with the instance.
234: * @throws IllegalStateException - if the instance invokes this method while
235: * the instance is in a state that does not allow the instance to
236: * invoke this method, or if the instance does not have a local
237: * interface.
238: */
239: public EJBLocalObject get2EJBLocalObject()
240: throws IllegalStateException {
241: return getEJBLocalObject(false);
242: }
243:
244: /**
245: * Obtains the primary key of the EJB object that is currently associated
246: * with this instance.
247: * @return The EJB object currently associated with the instance.
248: * @throws IllegalStateException Thrown if the instance invokes this method
249: * while the instance is in a state that does not allow the instance
250: * to invoke this method.
251: */
252: public Object getPrimaryKey() throws IllegalStateException {
253: if (TraceEjb.isDebugIc()) {
254: TraceEjb.interp.log(BasicLevel.DEBUG, "");
255: }
256: if (ismarkedremoved) {
257: TraceEjb.logger.log(BasicLevel.ERROR, "marked removed : "
258: + this );
259: throw new IllegalStateException("EJB is removed");
260: }
261: if (bs == null) {
262: TraceEjb.logger.log(BasicLevel.ERROR, "no EntitySwitch : "
263: + this );
264: throw new IllegalStateException("no EntitySwitch");
265: }
266: return bs.getPrimaryKey();
267: }
268:
269: // -------------------------------------------------------------------
270: // Synchronization implementation
271: // -------------------------------------------------------------------
272:
273: /**
274: * This method is typically called at beforeCompletion
275: */
276: public void beforeCompletion() {
277: if (TraceEjb.isDebugContext()) {
278: TraceEjb.context.log(BasicLevel.DEBUG, "");
279: }
280:
281: // If isMarkedRemoved, no need to do all this.
282: if (ismarkedremoved) {
283: if (TraceEjb.isDebugContext()) {
284: TraceEjb.context.log(BasicLevel.DEBUG,
285: "ismarkedremoved -> no ejbStore");
286: }
287: return;
288: }
289:
290: if (beanCoord == null) {
291: // should not go here!
292: TraceEjb.logger.log(BasicLevel.ERROR, "no tx for " + this );
293: }
294:
295: // First check that the primary key is not null.
296: // We must avoid accessing the DataBase when ejbCreate has failed.
297: if (isnewinstance) {
298: try {
299: if (getPrimaryKey() == null) {
300: // We do not force abort creation in order
301: // to be able to catch the CreateException
302: // otherwise CreateException will be overloaded
303: // in postInvoke by "cannot commit exception"
304: return;
305: }
306: } catch (IllegalStateException e) {
307: return;
308: }
309: }
310:
311: try {
312: storeIfModified();
313: } catch (EJBException e) {
314: // Could not store bean data: set transaction rollback only
315: abortTransaction();
316: }
317: }
318:
319: /**
320: * This method is typically called after the transaction is committed.
321: * @param status The status of the transaction completion.
322: */
323: public void afterCompletion(int status) {
324:
325: boolean committed = (status == Status.STATUS_COMMITTED);
326: if (TraceEjb.isDebugContext()) {
327: if (committed) {
328: TraceEjb.context.log(BasicLevel.DEBUG, "committed");
329: } else {
330: TraceEjb.context.log(BasicLevel.DEBUG, "rolledback");
331: }
332: }
333:
334: // Just check that we knew we were in a transaction (debug)
335: if (beanCoord == null) {
336: // should not go here!
337: TraceEjb.logger.log(BasicLevel.ERROR, "no tx for " + this );
338: return;
339: }
340: // Check also that the bs is not null (debug)
341: if (bs == null) {
342: // should not go here!
343: TraceEjb.logger.log(BasicLevel.ERROR,
344: "Context without EntitySwitch reference: " + this );
345: throw new EJBException("Context with no Entity Switch");
346: }
347:
348: // Let the EntitySwitch know that transaction is over and that it can
349: // now dispose of this Context for another transaction.
350: // no need to be in the correct component context ?
351: bs.txCompleted(beanCoord, committed);
352: }
353:
354: // -------------------------------------------------------------------
355: // Other public methods
356: // -------------------------------------------------------------------
357:
358: /**
359: * Raz Context before freeing it. This is mainly for garbage collector.
360: */
361: public void razEntityContext() {
362: if (TraceEjb.isDebugContext()) {
363: TraceEjb.context.log(BasicLevel.DEBUG, "");
364: }
365: bs = null;
366: beanCoord = null;
367: ismarkedremoved = false;
368: isnewinstance = false;
369: initialized = false;
370: }
371:
372: /**
373: * Detach this Context from tx
374: */
375: public void detachTx() {
376: if (TraceEjb.isDebugContext()) {
377: TraceEjb.context.log(BasicLevel.DEBUG, "");
378: }
379: beanCoord = null;
380: ismarkedremoved = false;
381: isnewinstance = false;
382: }
383:
384: /**
385: * Reinit Context for reuse
386: * @param bs - The Bean Switch this Context belongs to.
387: */
388: public boolean initEntityContext(JEntitySwitch bs) {
389: if (TraceEjb.isDebugContext()) {
390: TraceEjb.context.log(BasicLevel.DEBUG, "");
391: }
392: // setEntitySwitch must not set initialized (See create method)
393: setEntitySwitch(bs);
394: initialized = true;
395: // must consider the case where dirty was set too early (create case)
396: return dirty;
397: }
398:
399: /**
400: * Associate transaction to ths Context
401: * @param tx
402: * @return true if correctly associated, false if was already done.
403: */
404: public boolean setRunningTx(Transaction tx) {
405: TraceEjb.context.log(BasicLevel.DEBUG, "");
406: if (tx != null && beanCoord != null) {
407: TraceEjb.context
408: .log(BasicLevel.DEBUG, "Already associated");
409: return false;
410: }
411: if (bs == null) {
412: TraceEjb.logger.log(BasicLevel.ERROR,
413: "no Entity Switch for " + this );
414: throw new EJBException(
415: "No Entity Switch for this EJBContext");
416: }
417: beanCoord = tx;
418: return true;
419: }
420:
421: /**
422: * reuse EntityContext for another transaction. optimization: don't pass it
423: * by the pool.
424: * @param newtrans true if new transaction
425: */
426: public void reuseEntityContext(boolean newtrans) {
427: if (TraceEjb.isDebugContext()) {
428: TraceEjb.context.log(BasicLevel.DEBUG, "");
429: }
430:
431: // This prevents reusing an instance removed in the same transaction.
432: if (ismarkedremoved) {
433: TraceEjb.context.log(BasicLevel.WARN,
434: "Try to access a deleted object");
435: throw new NoSuchObjectLocalException(
436: "Instance has been removed");
437: }
438: if (newtrans) {
439: isnewinstance = false;
440: }
441: if (bs == null) {
442: TraceEjb.logger.log(BasicLevel.ERROR,
443: "no Entity Switch for " + this );
444: throw new EJBException("Internal Error");
445: }
446: }
447:
448: /**
449: * Set new instance flag
450: */
451: public void setNewInstance() {
452: isnewinstance = true;
453: }
454:
455: /**
456: * Mark this context as removed. Complete removing will be achieved at the
457: * end of the transaction.
458: * @throws RemoteException ejbRemove failed
459: * @throws RemoveException ejbRemove failed
460: */
461: public void setRemoved() throws RemoteException, RemoveException {
462: if (TraceEjb.isDebugContext()) {
463: TraceEjb.context.log(BasicLevel.DEBUG, "");
464: }
465: EntityBean eb = (EntityBean) myinstance;
466:
467: if (myinstance == null) {
468: TraceEjb.logger.log(BasicLevel.ERROR, "null instance!");
469: return;
470: }
471: // Set dirty to isolate transactions ???
472: setDirty(true);
473:
474: // call ejbRemove() on instance
475: eb.ejbRemove();
476: // getPrimaryKey and getEJBObject should work in ejbRemove.
477: // => we do this only after ejbRemove call.
478: ismarkedremoved = true;
479: }
480:
481: /**
482: * Check if context has been marked removed
483: * @return true when instance is marked removed.
484: */
485: public boolean isMarkedRemoved() {
486: return ismarkedremoved;
487: }
488:
489: /**
490: * Check if context is a newly created instance
491: * @return true when instance has been created in the current transaction.
492: */
493: public boolean isNewInstance() {
494: return isnewinstance;
495: }
496:
497: /**
498: * Returns the bean instance of this context Used in the generated classes
499: * to retrieve the instance
500: * @return the bean instance
501: * @throws RemoteException if no instance.
502: */
503: // RemoteException is throwned to allow simpler genic templates
504: public EntityBean getInstance() throws RemoteException {
505: if (myinstance == null) {
506: TraceEjb.logger.log(BasicLevel.ERROR, "null!");
507: throw new RemoteException("No instance available");
508: }
509: return (EntityBean) myinstance;
510: }
511:
512: /**
513: * JEntityFactory accessor
514: * @return the JEntityFactory
515: */
516: public JEntityFactory getEntityFactory() {
517: return (JEntityFactory) bf;
518: }
519:
520: /**
521: * JEntitySwitch accessor
522: * @return the JEntitySwitch
523: */
524: public JEntitySwitch getEntitySwitch() {
525: if (bs == null) {
526: TraceEjb.logger.log(BasicLevel.WARN, "no ES for ctx:"
527: + this );
528: }
529: return (JEntitySwitch) bs;
530: }
531:
532: /**
533: * @return True if Entity Switch has been already associated
534: */
535: public boolean isInitialized() {
536: return bs != null;
537: }
538:
539: /**
540: * set the EntitySwitch
541: * @param bs - the EntitySwitch
542: */
543: public void setEntitySwitch(JEntitySwitch bs) {
544: this .bs = bs;
545: TraceEjb.context.log(BasicLevel.DEBUG, "");
546: if (ismarkedremoved) {
547: TraceEjb.logger.log(BasicLevel.ERROR,
548: "ismarkedremoved WAS SET");
549: Thread.dumpStack();
550: ismarkedremoved = false;
551: }
552: // RO = never write. For other policies, must notify writing if lazyRegister
553: // has been set. This is only possible in CMP2.
554: mustnotifywriting = (bs.getPolicy() != EntityDesc.LOCK_READ_ONLY)
555: && bs.lazyRegistering();
556: }
557:
558: /**
559: * @return true if instance has been modified
560: */
561: public boolean isDirty() {
562: return dirty;
563: }
564:
565: /**
566: * Set the dirty flag: true = instance modified.
567: */
568: public void setDirty(boolean d) {
569: if (d) {
570: TraceEjb.context.log(BasicLevel.DEBUG, "true");
571: // Notify the EntitySwitch at 1st writing
572: if (mustnotifywriting && initialized) {
573: // retrieve the current transaction.
574: Transaction tx = null;
575: try {
576: tx = tm.getTransaction();
577: } catch (SystemException e) {
578: TraceEjb.context.log(BasicLevel.ERROR,
579: "getTransaction failed", e);
580: }
581: if (tx == null) {
582: TraceEjb.logger
583: .log(BasicLevel.WARN,
584: "You should not modify the bean without a transactional context");
585: } else {
586: bs.notifyWriting(tx, this );
587: }
588: } else {
589: if (!initialized) {
590: TraceEjb.context.log(BasicLevel.DEBUG,
591: "not initialized");
592: }
593: }
594: }
595: // Must be called after notifyWriting
596: dirty = d;
597: }
598:
599: /**
600: * Persistence: write data on storage
601: */
602: public void storeIfModified() {
603:
604: EntityBean eb = (EntityBean) myinstance;
605:
606: if (ismarkedremoved) {
607: // TODO something smart to do here ?
608: if (TraceEjb.isDebugContext()) {
609: TraceEjb.context
610: .log(BasicLevel.DEBUG, "marked removed");
611: }
612: return;
613: }
614: if (TraceEjb.isDebugContext()) {
615: TraceEjb.context.log(BasicLevel.DEBUG, "");
616: }
617:
618: try {
619: // this method can fail if database serializes transactions
620: // This may do nothing if bean has not been modified (isModified
621: // optimization)
622: eb.ejbStore();
623: } catch (RemoteException e) {
624: throw new EJBException("Exception while storing data", e);
625: } catch (EJBException e) {
626: TraceEjb.logger.log(BasicLevel.ERROR,
627: "raised EJBException ", e);
628: throw e;
629: } catch (RuntimeException e) {
630: TraceEjb.logger.log(BasicLevel.ERROR,
631: "runtime exception: ", e);
632: throw new EJBException("Exception while storing data", e);
633: } catch (Error e) {
634: TraceEjb.logger.log(BasicLevel.ERROR, "error: ", e);
635: throw new EJBException("Error while storing data");
636: }
637: }
638:
639: /**
640: * passivate this instance
641: * @return true if passivated.
642: */
643: public boolean passivate() {
644:
645: if (TraceEjb.isDebugContext()) {
646: TraceEjb.context.log(BasicLevel.DEBUG, "");
647: }
648:
649: EntityBean eb = (EntityBean) myinstance;
650: setState(1);
651:
652: try {
653: eb.ejbPassivate();
654: } catch (Exception e) {
655: TraceEjb.logger.log(BasicLevel.ERROR,
656: "ejbPassivate failed", e);
657: } catch (Error e) {
658: TraceEjb.logger.log(BasicLevel.ERROR, "ejbPassivate error",
659: e);
660: }
661: return true;
662: }
663:
664: /**
665: * Activate instance.
666: * @param doactivate True if ejbActivate() is called before ejbLoad()
667: */
668: public void activate(boolean doactivate) {
669: if (TraceEjb.isDebugContext()) {
670: TraceEjb.context.log(BasicLevel.DEBUG, "");
671: }
672:
673: EntityBean eb = (EntityBean) myinstance;
674:
675: try {
676: if (doactivate) {
677: setState(1);
678: // Call ejbActivate
679: eb.ejbActivate();
680: }
681: setState(2);
682: // Load bean state from database
683: eb.ejbLoad();
684: } catch (RemoteException e) {
685: TraceEjb.logger.log(BasicLevel.ERROR, "remote exception: ",
686: e);
687: throw new EJBException("Cannot activate bean", e);
688: } catch (RuntimeException e) {
689: TraceEjb.logger.log(BasicLevel.ERROR,
690: "runtime exception: ", e);
691: throw new EJBException("Cannot activate bean", e);
692: } catch (Error e) {
693: TraceEjb.logger.log(BasicLevel.ERROR, "error: ", e);
694: throw new EJBException("Cannot activate bean");
695: }
696: }
697:
698: // ----------------------------------------------------------------
699: // private methods
700: // ----------------------------------------------------------------
701:
702: /**
703: * abort current transaction if an exception occured while accessing the
704: * bean state (load, store, ...)
705: */
706: private void abortTransaction() {
707: if (TraceEjb.isDebugContext()) {
708: TraceEjb.context.log(BasicLevel.DEBUG, "");
709: }
710:
711: // If we are inside a transaction, it must be rolled back
712: if (beanCoord != null) {
713: try {
714: beanCoord.setRollbackOnly();
715: } catch (SystemException e) {
716: TraceEjb.logger.log(BasicLevel.ERROR,
717: "cannot setRollbackOnly", e);
718: }
719: }
720: }
721:
722: }
|