KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jboss > ejb > plugins > EntitySynchronizationInterceptor


1 /*
2  * JBoss, Home of Professional Open Source
3  * Copyright 2005, JBoss Inc., and individual contributors as indicated
4  * by the @authors tag. See the copyright.txt in the distribution for a
5  * full listing of individual contributors.
6  *
7  * This is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as
9  * published by the Free Software Foundation; either version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * This software is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this software; if not, write to the Free
19  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21  */

22 package org.jboss.ejb.plugins;
23
24 import java.lang.reflect.Method JavaDoc;
25 import java.util.TimerTask JavaDoc;
26
27 import javax.ejb.EJBException JavaDoc;
28 import javax.transaction.RollbackException JavaDoc;
29 import javax.transaction.Status JavaDoc;
30 import javax.transaction.Synchronization JavaDoc;
31 import javax.transaction.Transaction JavaDoc;
32
33 import org.jboss.ejb.BeanLock;
34 import org.jboss.ejb.Container;
35 import org.jboss.ejb.EntityCache;
36 import org.jboss.ejb.EntityContainer;
37 import org.jboss.ejb.EntityEnterpriseContext;
38 import org.jboss.ejb.GlobalTxEntityMap;
39 import org.jboss.invocation.Invocation;
40 import org.jboss.metadata.ConfigurationMetaData;
41 import org.jboss.util.NestedRuntimeException;
42
43 /**
44  * The role of this interceptor is to synchronize the state of the cache with
45  * the underlying storage. It does this with the ejbLoad and ejbStore
46  * semantics of the EJB specification. In the presence of a transaction this
47  * is triggered by transaction demarcation. It registers a callback with the
48  * underlying transaction monitor through the JTA interfaces. If there is no
49  * transaction the policy is to store state upon returning from invocation.
50  * The synchronization polices A,B,C of the specification are taken care of
51  * here.
52  *
53  * <p><b>WARNING: critical code</b>, get approval from senior developers
54  * before changing.
55  *
56  * @author <a HREF="mailto:marc.fleury@jboss.org">Marc Fleury</a>
57  * @author <a HREF="mailto:Scott.Stark@jboss.org">Scott Stark</a>
58  * @author <a HREF="mailto:bill@burkecentral.com">Bill Burke</a>
59  * @version $Revision: 44565 $
60  */

61 public class EntitySynchronizationInterceptor extends AbstractInterceptor
62 {
63    /** Task for refreshing contexts */
64    private ValidContextsRefresher vcr;
65
66    /**
67     * The current commit option.
68     */

69    protected int commitOption;
70
71    /**
72     * The refresh rate for commit option d
73     */

74    protected long optionDRefreshRate;
75
76    /**
77     * The container of this interceptor.
78     */

79    protected EntityContainer container;
80
81    public Container getContainer()
82    {
83       return container;
84    }
85
86    public void setContainer(Container container)
87    {
88       this.container = (EntityContainer) container;
89    }
90
91    public void create()
92            throws Exception JavaDoc
93    {
94
95       try
96       {
97          ConfigurationMetaData configuration = container.getBeanMetaData().getContainerConfiguration();
98          commitOption = configuration.getCommitOption();
99          optionDRefreshRate = configuration.getOptionDRefreshRate();
100       }
101       catch(Exception JavaDoc e)
102       {
103          log.warn(e.getMessage());
104       }
105    }
106
107    public void start()
108    {
109       try
110       {
111          //start up the validContexts thread if commit option D
112
if (commitOption == ConfigurationMetaData.D_COMMIT_OPTION)
113          {
114             vcr = new ValidContextsRefresher();
115             LRUEnterpriseContextCachePolicy.tasksTimer.schedule(vcr, optionDRefreshRate, optionDRefreshRate);
116             log.debug("Scheduled a cache flush every " + optionDRefreshRate/1000 + " seconds");
117          }
118       }
119       catch(Exception JavaDoc e)
120       {
121          vcr = null;
122          log.warn("problem scheduling valid contexts refresher", e);
123       }
124    }
125
126    public void stop()
127    {
128       if (vcr != null)
129       {
130          TimerTask JavaDoc temp = vcr;
131          vcr = null;
132          temp.cancel();
133       }
134    }
135
136    protected Synchronization JavaDoc createSynchronization(Transaction JavaDoc tx, EntityEnterpriseContext ctx)
137    {
138       return new InstanceSynchronization(tx, ctx);
139    }
140
141    /**
142     * Register a transaction synchronization callback with a context.
143     */

144    protected void register(EntityEnterpriseContext ctx, Transaction JavaDoc tx)
145    {
146       boolean trace = log.isTraceEnabled();
147       if(trace)
148          log.trace("register, ctx=" + ctx + ", tx=" + tx);
149
150       EntityContainer ctxContainer = null;
151       try
152       {
153          ctxContainer = (EntityContainer)ctx.getContainer();
154          if(!ctx.hasTxSynchronization())
155          {
156             // Create a new synchronization
157
Synchronization JavaDoc synch = createSynchronization(tx, ctx);
158
159             // We want to be notified when the transaction commits
160
tx.registerSynchronization(synch);
161
162             ctx.hasTxSynchronization(true);
163          }
164          //mark it dirty in global tx entity map if it is not read only
165
if(!ctxContainer.isReadOnly())
166          {
167             ctx.getTxAssociation().scheduleSync(tx, ctx);
168          }
169       }
170       catch(RollbackException JavaDoc e)
171       {
172          // The state in the instance is to be discarded, we force a reload of state
173
synchronized(ctx)
174          {
175             ctx.setValid(false);
176             ctx.hasTxSynchronization(false);
177             ctx.setTransaction(null);
178             ctx.setTxAssociation(GlobalTxEntityMap.NONE);
179          }
180          throw new EJBException JavaDoc(e);
181       }
182       catch(Throwable JavaDoc t)
183       {
184          // If anything goes wrong with the association remove the ctx-tx association
185
ctx.hasTxSynchronization(false);
186          ctx.setTxAssociation(GlobalTxEntityMap.NONE);
187          if(t instanceof RuntimeException JavaDoc)
188             throw (RuntimeException JavaDoc)t;
189          else if(t instanceof Error JavaDoc)
190             throw (Error JavaDoc)t;
191          else if(t instanceof Exception JavaDoc)
192             throw new EJBException JavaDoc((Exception JavaDoc)t);
193          else
194             throw new NestedRuntimeException(t);
195       }
196    }
197
198    public Object JavaDoc invokeHome(Invocation mi) throws Exception JavaDoc
199    {
200       EntityEnterpriseContext ctx = (EntityEnterpriseContext)mi.getEnterpriseContext();
201       Transaction JavaDoc tx = mi.getTransaction();
202
203       Object JavaDoc rtn = getNext().invokeHome(mi);
204
205       // An anonymous context was sent in, so if it has an id it is a real instance now
206
if(ctx.getId() != null)
207       {
208
209          // it doesn't need to be read, but it might have been changed from the db already.
210
ctx.setValid(true);
211
212          if(tx != null)
213          {
214             BeanLock lock = container.getLockManager().getLock(ctx.getCacheKey());
215             try
216             {
217                lock.schedule(mi);
218                register(ctx, tx); // Set tx
219
lock.endInvocation(mi);
220             }
221             finally
222             {
223                container.getLockManager().removeLockRef(lock.getId());
224             }
225          }
226       }
227       return rtn;
228    }
229
230    public Object JavaDoc invoke(Invocation mi) throws Exception JavaDoc
231    {
232       // We are going to work with the context a lot
233
EntityEnterpriseContext ctx = (EntityEnterpriseContext)mi.getEnterpriseContext();
234
235       // The Tx coming as part of the Method Invocation
236
Transaction JavaDoc tx = mi.getTransaction();
237
238       if(log.isTraceEnabled())
239          log.trace("invoke called for ctx " + ctx + ", tx=" + tx);
240
241       if(!ctx.isValid())
242       {
243          container.getPersistenceManager().loadEntity(ctx);
244          ctx.setValid(true);
245       }
246
247       // mark the context as read only if this is a readonly method and the context
248
// was not already readonly
249
boolean didSetReadOnly = false;
250       if(!ctx.isReadOnly() &&
251          (container.isReadOnly() ||
252          container.getBeanMetaData().isMethodReadOnly(mi.getMethod())))
253       {
254          ctx.setReadOnly(true);
255          didSetReadOnly = true;
256       }
257
258       // So we can go on with the invocation
259

260       // Invocation with a running Transaction
261
try
262       {
263          if(tx != null && tx.getStatus() != Status.STATUS_NO_TRANSACTION)
264          {
265             // readonly does not synchronize, lock or belong with transaction.
266
boolean isReadOnly = container.isReadOnly();
267             if(isReadOnly == false)
268             {
269                Method JavaDoc method = mi.getMethod();
270                if(method != null)
271                   isReadOnly = container.getBeanMetaData().isMethodReadOnly(method.getName());
272             }
273             try
274             {
275                if(isReadOnly == false)
276                {
277                   // register the wrapper with the transaction monitor (but only
278
// register once). The transaction demarcation will trigger the
279
// storage operations
280
register(ctx, tx);
281                }
282
283                //Invoke down the chain
284
Object JavaDoc retVal = getNext().invoke(mi);
285
286                // Register again as a finder in the middle of a method
287
// will de-register this entity, and then the rest of the method can
288
// change fields which will never be stored
289
if(isReadOnly == false)
290                {
291                   // register the wrapper with the transaction monitor (but only
292
// register once). The transaction demarcation will trigger the
293
// storage operations
294
register(ctx, tx);
295                }
296
297                // return the return value
298
return retVal;
299             }
300             finally
301             {
302                // We were read-only and the context wasn't already synchronized, tidyup the cache
303
if(isReadOnly && ctx.hasTxSynchronization() == false)
304                {
305                   switch(commitOption)
306                   {
307                      // Keep instance active, but invalidate state
308
case ConfigurationMetaData.B_COMMIT_OPTION:
309                         // Invalidate state (there might be other points of entry)
310
ctx.setValid(false);
311                         break;
312
313                         // Invalidate everything AND Passivate instance
314
case ConfigurationMetaData.C_COMMIT_OPTION:
315                         try
316                         {
317                            // FIXME: We cannot passivate here, because previous
318
// interceptors work with the context, in particular
319
// the re-entrance interceptor is doing lock counting
320
// Just remove it from the cache
321
if(ctx.getId() != null)
322                               container.getInstanceCache().remove(ctx.getId());
323                         }
324                         catch(Exception JavaDoc e)
325                         {
326                            log.debug("Exception releasing context", e);
327                         }
328                         break;
329                   }
330                }
331             }
332          }
333          else
334          {
335             // No tx
336
try
337             {
338                Object JavaDoc result = getNext().invoke(mi);
339
340                // Store after each invocation -- not on exception though, or removal
341
// And skip reads too ("get" methods)
342
if(ctx.getId() != null && !container.isReadOnly())
343                {
344                   container.invokeEjbStore(ctx);
345                   container.storeEntity(ctx);
346                }
347
348                return result;
349             }
350             catch(Exception JavaDoc e)
351             {
352                // Exception - force reload on next call
353
ctx.setValid(false);
354                throw e;
355             }
356             finally
357             {
358                switch(commitOption)
359                {
360                   // Keep instance active, but invalidate state
361
case ConfigurationMetaData.B_COMMIT_OPTION:
362                      // Invalidate state (there might be other points of entry)
363
ctx.setValid(false);
364                      break;
365
366                      // Invalidate everything AND Passivate instance
367
case ConfigurationMetaData.C_COMMIT_OPTION:
368                      try
369                      {
370                         // Do not call release if getId() is null. This means that
371
// the entity has been removed from cache.
372
// release will schedule a passivation and this removed ctx
373
// could be put back into the cache!
374
// This is necessary because we have no lock, we
375
// don't want to return an instance to the pool that is
376
// being used
377
if(ctx.getId() != null)
378                            container.getInstanceCache().remove(ctx.getId());
379                      }
380                      catch(Exception JavaDoc e)
381                      {
382                         log.debug("Exception releasing context", e);
383                      }
384                      break;
385                }
386             }
387          }
388       }
389       finally
390       {
391          // if we marked the context as read only we need to reset it
392
if(didSetReadOnly)
393          {
394             ctx.setReadOnly(false);
395          }
396       }
397    }
398
399    protected class InstanceSynchronization
400            implements Synchronization JavaDoc
401    {
402       /**
403        * The transaction we follow.
404        */

405       protected Transaction JavaDoc tx;
406
407       /**
408        * The context we manage.
409        */

410       protected EntityEnterpriseContext ctx;
411
412       /**
413        * The context lock
414        */

415       protected BeanLock lock;
416
417       /**
418        * Create a new instance synchronization instance.
419        */

420       InstanceSynchronization(Transaction JavaDoc tx, EntityEnterpriseContext ctx)
421       {
422          this.tx = tx;
423          this.ctx = ctx;
424          this.lock = container.getLockManager().getLock(ctx.getCacheKey());
425       }
426
427       public void beforeCompletion()
428       {
429          //synchronization is handled by GlobalTxEntityMap.
430
}
431
432       public void afterCompletion(int status)
433       {
434          boolean trace = log.isTraceEnabled();
435
436          // This is an independent point of entry. We need to make sure the
437
// thread is associated with the right context class loader
438
ClassLoader JavaDoc oldCl = SecurityActions.getContextClassLoader();
439          boolean setCl = !oldCl.equals(container.getClassLoader());
440          if(setCl)
441          {
442             SecurityActions.setContextClassLoader(container.getClassLoader());
443          }
444
445          int commitOption = ctx.isPassivateAfterCommit() ?
446             ConfigurationMetaData.C_COMMIT_OPTION : EntitySynchronizationInterceptor.this.commitOption;
447
448          lock.sync();
449          // The context is no longer synchronized on the TX
450
ctx.hasTxSynchronization(false);
451          ctx.setTxAssociation(GlobalTxEntityMap.NONE);
452          ctx.setTransaction(null);
453          try
454          {
455             try
456             {
457                // If rolled back -> invalidate instance
458
if(status == Status.STATUS_ROLLEDBACK)
459                {
460                   // remove from the cache
461
container.getInstanceCache().remove(ctx.getCacheKey());
462                }
463                else
464                {
465                   switch(commitOption)
466                   {
467                      // Keep instance cached after tx commit
468
case ConfigurationMetaData.A_COMMIT_OPTION:
469                      case ConfigurationMetaData.D_COMMIT_OPTION:
470                         // The state is still valid (only point of access is us)
471
ctx.setValid(true);
472                         break;
473
474                         // Keep instance active, but invalidate state
475
case ConfigurationMetaData.B_COMMIT_OPTION:
476                         // Invalidate state (there might be other points of entry)
477
ctx.setValid(false);
478                         break;
479                         // Invalidate everything AND Passivate instance
480
case ConfigurationMetaData.C_COMMIT_OPTION:
481                         try
482                         {
483                            // We weren't removed, passivate
484
// Here we own the lock, so we don't try to passivate
485
// we just passivate
486
if(ctx.getId() != null)
487                            {
488                               container.getInstanceCache().remove(ctx.getId());
489                               container.getPersistenceManager().passivateEntity(ctx);
490                            }
491                            // If we get this far, we return to the pool
492
container.getInstancePool().free(ctx);
493                         }
494                         catch(Exception JavaDoc e)
495                         {
496                            log.debug("Exception releasing context", e);
497                         }
498                         break;
499                   }
500                }
501             }
502             finally
503             {
504                if(trace)
505                   log.trace("afterCompletion, clear tx for ctx=" + ctx + ", tx=" + tx);
506                lock.endTransaction(tx);
507
508                if(trace)
509                   log.trace("afterCompletion, sent notify on TxLock for ctx=" + ctx);
510             }
511          } // synchronized(lock)
512
finally
513          {
514             lock.releaseSync();
515             container.getLockManager().removeLockRef(lock.getId());
516             if(setCl)
517             {
518                SecurityActions.setContextClassLoader(oldCl);
519             }
520          }
521       }
522
523    }
524
525    /**
526     * Flushes the cache according to the optiond refresh rate.
527     */

528    class ValidContextsRefresher extends TimerTask JavaDoc
529    {
530       public ValidContextsRefresher()
531       {
532       }
533       
534       public void run()
535       {
536          // Guard against NPE at shutdown
537
if (container == null)
538          {
539             cancel();
540             return;
541          }
542          
543          if(log.isTraceEnabled())
544             log.trace("Flushing the valid contexts " + container.getBeanMetaData().getEjbName());
545
546          EntityCache cache = (EntityCache) container.getInstanceCache();
547          try
548          {
549             if(cache != null)
550                cache.flush();
551          }
552          catch (Throwable JavaDoc t)
553          {
554             log.debug("Ignored error while trying to flush() entity cache", t);
555          }
556       }
557    }
558 }
559
Popular Tags