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.Constructor;
25 import java.rmi.NoSuchObjectException;
26 import java.rmi.RemoteException;
27 import java.util.Map;
28
29 import org.jboss.deployment.DeploymentException;
30 import org.jboss.ejb.BeanLock;
31 import org.jboss.ejb.BeanLockExt;
32 import org.jboss.ejb.Container;
33 import org.jboss.ejb.EnterpriseContext;
34 import org.jboss.ejb.InstanceCache;
35 import org.jboss.logging.Logger;
36 import org.jboss.metadata.MetaData;
37 import org.jboss.metadata.XmlLoadable;
38 import org.jboss.monitor.MetricsConstants;
39 import org.jboss.monitor.Monitorable;
40 import org.jboss.monitor.client.BeanCacheSnapshot;
41 import org.jboss.util.CachePolicy;
42 import org.w3c.dom.Element;
43
44 /**
45 * Base class for caches of entity and stateful beans. <p>
46 * It manages the cache entries through a {@link CachePolicy} object;
47 * the implementation of the cache policy object must respect the following
48 * requirements:
49 * <ul>
50 * <li> Have a public constructor that takes a single argument of type
51 * AbstractInstanceCache.class or a subclass
52 * </ul>
53 *
54 * @author <a href="mailto:simone.bordet@compaq.com">Simone Bordet</a>
55 * @author <a href="bill@burkecentral.com">Bill Burke</a>
56 * @author <a href="marc.fleury@jboss.org">Marc Fleury</a>
57 * @author Scott.Stark@jboss.org
58 * @version $Revision: 43998 $
59 * @jmx:mbean
60 */
61 public abstract class AbstractInstanceCache
62 implements InstanceCache, XmlLoadable, Monitorable, MetricsConstants,
63 AbstractInstanceCacheMBean
64 {
65 // Constants -----------------------------------------------------
66
67 // Attributes ----------------------------------------------------
68 protected static Logger log = Logger.getLogger(AbstractInstanceCache.class);
69
70 /* The object that is delegated to implement the desired caching policy */
71 private CachePolicy m_cache;
72 /* The mutex object for the cache */
73 private final Object m_cacheLock = new Object();
74
75 // Static --------------------------------------------------------
76
77 // Constructors --------------------------------------------------
78
79 // Monitorable implementation ------------------------------------
80 public void sample(Object s)
81 {
82 if( m_cache == null )
83 return;
84
85 synchronized (getCacheLock())
86 {
87 BeanCacheSnapshot snapshot = (BeanCacheSnapshot)s;
88 snapshot.m_passivatingBeans = 0;
89 CachePolicy policy = getCache();
90 if (policy instanceof Monitorable)
91 {
92 ((Monitorable)policy).sample(s);
93 }
94 }
95 }
96 public Map retrieveStatistic()
97 {
98 return null;
99 }
100 public void resetStatistic()
101 {
102 }
103
104 // Public --------------------------------------------------------
105 /* From InstanceCache interface */
106 public EnterpriseContext get(Object id)
107 throws RemoteException, NoSuchObjectException
108 {
109 if (id == null) throw new IllegalArgumentException("Can't get an object with a null key");
110
111 EnterpriseContext ctx;
112
113 synchronized (getCacheLock())
114 {
115 CachePolicy cache = getCache();
116 ctx = (EnterpriseContext)cache.get(id);
117 if (ctx == null)
118 {
119 try
120 {
121 ctx = acquireContext();
122 setKey(id, ctx);
123 if (doActivate(ctx) == false)
124 // This is a recursive activation
125 return ctx;
126 logActivation(id);
127 // the cache will throw an IllegalStateException if we try to insert
128 // something that is in the cache already, so we don't check here
129 cache.insert(id, ctx);
130 }
131 catch (Throwable x)
132 {
133 log.debug("Activation failure", x);
134 throw new NoSuchObjectException(x.getMessage());
135 }
136 }
137 }
138
139 return ctx;
140 }
141
142 /* From InstanceCache interface */
143 public void insert(EnterpriseContext ctx)
144 {
145 if (ctx == null) throw new IllegalArgumentException("Can't insert a null object in the cache");
146
147 Object key = getKey(ctx);
148 synchronized (getCacheLock())
149 {
150 // the cache will throw an IllegalStateException if we try to insert
151 // something that is in the cache already, so we don't check here
152 getCache().insert(key, ctx);
153 }
154 }
155
156 /**
157 * Tries to passivate the instance. If the instance is in use then the instance
158 * will be passivated later according to the container's commit option and max age.
159 */
160 protected void tryToPassivate(EnterpriseContext ctx)
161 {
162 tryToPassivate(ctx, false);
163 }
164
165 /**
166 * Tries to passivate the instance. If the instance is in use and passivateAfterCommit
167 * parameter is true then the instance will passivated after the transaction commits.
168 * Otherwise, the instance will be passivated later according to the container's
169 * commit option and max age.
170 */
171 protected void tryToPassivate(EnterpriseContext ctx, boolean passivateAfterCommit)
172 {
173 Object id = ctx.getId();
174 if (id == null) return;
175 BeanLock lock = getContainer().getLockManager().getLock(id);
176 boolean lockedBean = false;
177 try
178 {
179 /* If this is a BeanLockExt only attempt the lock as the call to
180 remove is going to have to acquire the cache lock, but this may already
181 be held since this method is called by passivation policies without
182 the cache lock. This can lead to a deadlock as in the case of a size based
183 eviction during a cache get attempts to lock the bean that has been
184 locked by an age based background thread as seen in bug 987389 on
185 sourceforge.
186 */
187 if( lock instanceof BeanLockExt )
188 {
189 BeanLockExt lock2 = (BeanLockExt) lock;
190 lockedBean = lock2.attemptSync();
191 if( lockedBean == false )
192 {
193 unableToPassivateDueToCtxLock(ctx, passivateAfterCommit);
194 return;
195 }
196 }
197 else
198 {
199 // Use the blocking sync
200 lock.sync();
201 lockedBean = true;
202 }
203
204 if (canPassivate(ctx))
205 {
206 try
207 {
208 remove(id);
209 passivate(ctx);
210 freeContext(ctx);
211 }
212 catch (Exception ignored)
213 {
214 log.warn("failed to passivate, id="+id, ignored);
215 }
216 }
217 else
218 {
219 // Touch the entry to make it MRU
220 synchronized (getCacheLock())
221 {
222 getCache().get(id);
223 }
224
225 unableToPassivateDueToCtxLock(ctx, passivateAfterCommit);
226 }
227 }
228 finally
229 {
230 if( lockedBean )
231 lock.releaseSync();
232 getContainer().getLockManager().removeLockRef(id);
233 }
234 }
235
236 /**
237 * Passivates and removes the instance from the cache.
238 * If the instance is in use then removal and passivation will be scheduled until
239 * after transaction ends
240 */
241 public void release(EnterpriseContext ctx)
242 {
243 if (ctx == null) throw new IllegalArgumentException("Can't release a null object");
244
245 // Here I remove the bean; call to remove(id) is wrong
246 // cause will remove also the cache lock that is needed
247 // by the passivation, that eventually will remove it.
248 /* the removal should only be done if the instance is not in use.
249 this is taken care of in tryToPassivate
250 Object id = getKey(ctx);
251 synchronized (getCacheLock())
252 {
253 if (getCache().peek(id) != null)
254 getCache().remove(id);
255 }
256 */
257 tryToPassivate(ctx, true);
258 }
259
260 /**
261 * From InstanceCache interface
262 * @jmx:managed-operation
263 */
264 public void remove(Object id)
265 {
266 if (id == null) throw new IllegalArgumentException("Can't remove an object using a null key");
267
268 synchronized (getCacheLock())
269 {
270 if (getCache().peek(id) != null)
271 {
272 getCache().remove(id);
273 }
274 }
275 }
276
277 public boolean isActive(Object id)
278 {
279 // Check whether an object with the given id is available in the cache
280 synchronized (getCacheLock())
281 {
282 return getCache().peek(id) != null;
283 }
284 }
285
286 /** Get the current cache size
287 * @jmx:managed-attribute
288 * @return the size of the cache
289 */
290 public long getCacheSize()
291 {
292 int cacheSize = m_cache != null ? m_cache.size() : 0;
293 return cacheSize;
294 }
295 /** Flush the cache.
296 * @jmx:managed-operation
297 */
298 public void flush()
299 {
300 if( m_cache != null )
301 m_cache.flush();
302 }
303 /** Get the passivated count.
304 * @jmx:managed-attribute
305 * @return the number of passivated instances.
306 */
307 public long getPassivatedCount()
308 {
309 return 0;
310 }
311
312 /**
313 * Display the cache policy.
314 *
315 * @jmx:managed-attribute
316 * @return the cache policy as a string.
317 */
318 public String getCachePolicyString()
319 {
320 return m_cache.toString();
321 }
322
323 // XmlLoadable implementation ----------------------------------------------
324 public void importXml(Element element) throws DeploymentException
325 {
326 // This one is mandatory
327 String p = MetaData.getElementContent(MetaData.getUniqueChild(element, "cache-policy"));
328 try
329 {
330 Class cls = SecurityActions.getContextClassLoader().loadClass(p);
331 Constructor ctor = cls.getConstructor(new Class[] {AbstractInstanceCache.class});
332 m_cache = (CachePolicy)ctor.newInstance(new Object[] {this});
333 }
334 catch (Exception x)
335 {
336 throw new DeploymentException("Can't create cache policy", x);
337 }
338
339 Element policyConf = MetaData.getOptionalChild(element, "cache-policy-conf");
340 if (policyConf != null)
341 {
342 if (m_cache instanceof XmlLoadable)
343 {
344 try
345 {
346 ((XmlLoadable)m_cache).importXml(policyConf);
347 }
348 catch (Exception x)
349 {
350 throw new DeploymentException("Can't import policy configuration", x);
351 }
352 }
353 }
354 }
355
356 /* From Service interface*/
357 public void create() throws Exception
358 {
359 getCache().create();
360 }
361 /* From Service interface*/
362 public void start() throws Exception
363 {
364 getCache().start();
365 }
366 /* From Service interface*/
367 public void stop()
368 {
369 // Empty the cache
370 synchronized (getCacheLock())
371 {
372 getCache().stop();
373 }
374 }
375 /* From Service interface*/
376 public void destroy()
377 {
378 synchronized (getCacheLock())
379 {
380 getCache().destroy();
381 }
382 this.m_cache = null;
383 }
384
385 // Y overrides ---------------------------------------------------
386
387 // Package protected ---------------------------------------------
388
389 // Protected -----------------------------------------------------
390 protected void logActivation(Object id)
391 {
392 if( log.isTraceEnabled() )
393 {
394 StringBuffer m_buffer=new StringBuffer(100);
395 m_buffer.append("Activated bean ");
396 m_buffer.append(getContainer().getBeanMetaData().getEjbName());
397 m_buffer.append(" with id = ");
398 m_buffer.append(id);
399 log.trace(m_buffer.toString());
400 }
401 }
402
403 protected void logPassivation(Object id)
404 {
405 if( log.isTraceEnabled() )
406 {
407 StringBuffer m_buffer=new StringBuffer(100);
408 m_buffer.append("Passivated bean ");
409 m_buffer.append(getContainer().getBeanMetaData().getEjbName());
410 m_buffer.append(" with id = ");
411 m_buffer.append(id);
412 log.trace(m_buffer.toString());
413 }
414 }
415
416 protected void unableToPassivateDueToCtxLock(EnterpriseContext ctx, boolean passivateAfterCommit)
417 {
418 log.warn("Unable to passivate due to ctx lock, id="+ctx.getId());
419 }
420
421 /**
422 * Returns the container for this cache.
423 */
424 protected abstract Container getContainer();
425 /**
426 * Returns the cache policy used for this cache.
427 */
428 protected CachePolicy getCache() {return m_cache;}
429 /**
430 * Returns the mutex used to sync access to the cache policy object
431 */
432 public Object getCacheLock()
433 {
434 return m_cacheLock;
435 }
436 /**
437 * Passivates the given EnterpriseContext
438 */
439 protected abstract void passivate(EnterpriseContext ctx) throws RemoteException;
440 /**
441 * Activates the given EnterpriseContext
442 */
443 protected abstract void activate(EnterpriseContext ctx) throws RemoteException;
444 /**
445 * Activate the given EnterpriseContext
446 *
447 * @param ctx the context
448 * @return false if we recursively activating
449 * @throws RemoteException for any error
450 */
451 protected boolean doActivate(EnterpriseContext ctx) throws RemoteException
452 {
453 activate(ctx);
454 return true;
455 }
456 /**
457 * Acquires an EnterpriseContext from the pool
458 */
459 protected abstract EnterpriseContext acquireContext() throws Exception;
460 /**
461 * Frees the given EnterpriseContext to the pool
462 */
463 protected abstract void freeContext(EnterpriseContext ctx);
464 /**
465 * Returns the key used by the cache to map the given context
466 */
467 protected abstract Object getKey(EnterpriseContext ctx);
468 /**
469 * Sets the given id as key for the given context
470 */
471 protected abstract void setKey(Object id, EnterpriseContext ctx);
472 /**
473 * Returns whether the given context can be passivated or not
474 *
475 */
476 protected abstract boolean canPassivate(EnterpriseContext ctx);
477
478 // Private -------------------------------------------------------
479
480 // Inner classes -------------------------------------------------
481 }