View Javadoc

1   /**
2    *  Copyright 2003-2007 Greg Luck
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache.management;
18  
19  import net.sf.ehcache.CacheException;
20  import net.sf.ehcache.Status;
21  import net.sf.ehcache.event.CacheManagerEventListener;
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  
25  import javax.management.InstanceAlreadyExistsException;
26  import javax.management.MBeanRegistrationException;
27  import javax.management.MBeanServer;
28  import javax.management.MalformedObjectNameException;
29  import javax.management.NotCompliantMBeanException;
30  import javax.management.ObjectName;
31  import java.util.Iterator;
32  import java.util.List;
33  import java.util.Set;
34  
35  /**
36   * Ehcache CacheManagers and Caches have lifecycles. Often normal use of a CacheManager will be
37   * to shut it down and create a new one from within a running JVM. For example, in JEE environments,
38   * applications are often undeployed and then redeployed. A servlet listener, {@link net.sf.ehcache.constructs.web.ShutdownListener}
39   * enables this to be detected and the CacheManager shutdown.
40   * <p/>
41   * When a CacheManager is shut down we need to ensure there is no memory, resource or thread leakage.
42   * An MBeanServer, particularly a platform MBeanServer, can be expected to exist for the lifespan of the JVM.
43   * Accordingly, we need to deregister them when needed without creating a leakage.
44   * <p/>
45   * The second purpose of this class (and this package) is to keep management concerns away from the core ehcache packages.
46   * That way, JMX is not a required dependency, but rather an optional one.
47   *
48   * @author Greg Luck
49   * @version $Id: ManagementService.java 512 2007-07-10 09:18:45Z gregluck $
50   * @since 1.3
51   */
52  public final class ManagementService implements CacheManagerEventListener {
53  
54      private static final Log LOG = LogFactory.getLog(ManagementService.class.getName());
55  
56      private MBeanServer mBeanServer;
57      private net.sf.ehcache.CacheManager backingCacheManager;
58      private boolean registerCacheManager;
59      private boolean registerCaches;
60      private boolean registerCacheConfigurations;
61      private boolean registerCacheStatistics;
62      private Status status;
63  
64      /**
65       * Require use of the factory method
66       */
67      private ManagementService() {
68  
69      }
70  
71      /**
72       * This method causes the selected monitoring options to be be registered
73       * with the provided MBeanServer for caches in the given CacheManager.
74       * <p/>
75       * While registering the CacheManager enables traversal to all of the other items,
76       * this requires programmatic traversal. The other options allow entry points closer
77       * to an item of interest and are more accessible from JMX management tools like JConsole.
78       * Moreover CacheManager and Cache are not serializable, so remote monitoring is not possible for CacheManager
79       * or Cache, while CacheStatistics and CacheConfiguration are. Finally CacheManager and Cache enable
80       * management operations to be performed.
81       * <p/>
82       * Once monitoring is enabled caches will automatically added and removed from the MBeanServer
83       * as they are added and disposed of from the CacheManager. When the CacheManager itself shutsdown
84       * all registered MBeans will be unregistered.
85       *
86       * @param cacheManager the CacheManager to listen to
87       * @param mBeanServer the MBeanServer to register MBeans to
88       * @param registerCacheManager Whether to register the CacheManager MBean
89       * @param registerCaches Whether to register the Cache MBeans
90       * @param registerCacheConfigurations Whether to register the CacheConfiguration MBeans
91       * @param registerCacheStatistics Whether to register the CacheStatistics MBeans
92       */
93      public static void registerMBeans(
94              net.sf.ehcache.CacheManager cacheManager,
95              MBeanServer mBeanServer,
96              boolean registerCacheManager,
97              boolean registerCaches,
98              boolean registerCacheConfigurations,
99              boolean registerCacheStatistics) throws CacheException {
100 
101         ManagementService registry = new ManagementService();
102         registry.status = Status.STATUS_UNINITIALISED;
103         registry.backingCacheManager = cacheManager;
104         registry.mBeanServer = mBeanServer;
105         registry.registerCacheManager = registerCacheManager;
106         registry.registerCaches = registerCaches;
107         registry.registerCacheConfigurations = registerCacheConfigurations;
108         registry.registerCacheStatistics = registerCacheStatistics;
109 
110         registry.init();
111     }
112 
113 
114     /**
115      * Call to start the listeners and do any other required initialisation.
116      * Once intialised, it registers itself as a CacheManageEvenListener with the backing CacheManager, so
117      * that it can participate in lifecycle and other events.
118      *
119      * @throws net.sf.ehcache.CacheException - all exceptions are wrapped in CacheException
120      */
121     public void init() throws CacheException {
122         CacheManager cacheManager = new CacheManager(backingCacheManager);
123         try {
124             registerCacheManager(cacheManager);
125 
126             List caches = cacheManager.getCaches();
127             for (int i = 0; i < caches.size(); i++) {
128                 Cache cache = (Cache) caches.get(i);
129                 registerCachesIfRequired(cache);
130                 registerCacheStatisticsIfRequired(cache);
131                 registerCacheConfigurationIfRequired(cache);
132             }
133         } catch (Exception e) {
134             throw new CacheException(e);
135         }
136         status = Status.STATUS_ALIVE;
137         backingCacheManager.getCacheManagerEventListenerRegistry().registerListener(this);
138     }
139 
140     private void registerCacheManager(CacheManager cacheManager) throws InstanceAlreadyExistsException,
141             MBeanRegistrationException, NotCompliantMBeanException {
142         if (registerCacheManager) {
143             mBeanServer.registerMBean(cacheManager, cacheManager.getObjectName());
144         }
145     }
146 
147     private void registerCacheConfigurationIfRequired(Cache cache) throws InstanceAlreadyExistsException,
148             MBeanRegistrationException, NotCompliantMBeanException {
149         if (registerCacheConfigurations) {
150             CacheConfiguration cacheConfiguration = cache.getCacheConfiguration();
151             mBeanServer.registerMBean(cacheConfiguration, cacheConfiguration.getObjectName());
152         }
153     }
154 
155     private void registerCacheStatisticsIfRequired(Cache cache) throws InstanceAlreadyExistsException,
156             MBeanRegistrationException, NotCompliantMBeanException {
157         if (registerCacheStatistics) {
158             CacheStatistics cacheStatistics = cache.getStatistics();
159             mBeanServer.registerMBean(cacheStatistics, cacheStatistics.getObjectName());
160         }
161     }
162 
163     private void registerCachesIfRequired(Cache cache) throws InstanceAlreadyExistsException,
164             MBeanRegistrationException, NotCompliantMBeanException {
165         if (registerCaches) {
166             mBeanServer.registerMBean(cache, cache.getObjectName());
167         }
168     }
169 
170     /**
171      * Returns the listener status.
172      *
173      * @return the status at the point in time the method is called
174      */
175     public Status getStatus() {
176         return status;
177     }
178 
179     /**
180      * Stop the listener and free any resources.
181      * Removes registered ObjectNames
182      *
183      * @throws net.sf.ehcache.CacheException - all exceptions are wrapped in CacheException
184      */
185     public void dispose() throws CacheException {
186         Set registeredObjectNames = null;
187 
188         try {
189             //CacheManager MBean
190             registeredObjectNames = mBeanServer.queryNames(CacheManager.createObjectName(backingCacheManager), null);
191             //Other MBeans for this CacheManager
192             registeredObjectNames.addAll(mBeanServer.queryNames(new ObjectName("net.sf.ehcache:*,CacheManager="
193                     + backingCacheManager.toString()), null));
194         } catch (MalformedObjectNameException e) {
195             //this should not happen
196             LOG.error("Error querying MBeanServer. Error was " + e.getMessage(), e);
197         }
198         for (Iterator iterator = registeredObjectNames.iterator(); iterator.hasNext();) {
199             ObjectName objectName = (ObjectName) iterator.next();
200             try {
201                 mBeanServer.unregisterMBean(objectName);
202             } catch (Exception e) {
203                 LOG.error("Error unregistering object instance " + objectName
204                         + " . Error was " + e.getMessage(), e);
205             }
206         }
207         status = Status.STATUS_SHUTDOWN;
208     }
209 
210     /**
211      * Called immediately after a cache has been added and activated.
212      * <p/>
213      * Note that the CacheManager calls this method from a synchronized method. Any attempt to
214      * call a synchronized method on CacheManager from this method will cause a deadlock.
215      * <p/>
216      * Note that activation will also cause a CacheEventListener status change notification
217      * from {@link net.sf.ehcache.Status#STATUS_UNINITIALISED} to
218      * {@link net.sf.ehcache.Status#STATUS_ALIVE}. Care should be taken on processing that
219      * notification because:
220      * <ul>
221      * <li>the cache will not yet be accessible from the CacheManager.
222      * <li>the addCaches methods which cause this notification are synchronized on the
223      * CacheManager. An attempt to call {@link net.sf.ehcache.CacheManager#getEhcache(String)}
224      * will cause a deadlock.
225      * </ul>
226      * The calling method will block until this method returns.
227      * <p/>
228      *
229      * @param cacheName the name of the <code>Cache</code> the operation relates to
230      * @see net.sf.ehcache.event.CacheEventListener
231      */
232     public void notifyCacheAdded(String cacheName) {
233         if (registerCaches || registerCacheStatistics || registerCacheConfigurations) {
234             Cache cache = new Cache(backingCacheManager.getCache(cacheName));
235             try {
236                 registerCachesIfRequired(cache);
237                 registerCacheStatisticsIfRequired(cache);
238                 registerCacheConfigurationIfRequired(cache);
239             } catch (Exception e) {
240                 LOG.error("Error registering cache for management for " + cache.getObjectName()
241                         + " . Error was " + e.getMessage(), e);
242             }
243         }
244     }
245 
246     /**
247      * Called immediately after a cache has been disposed and removed. The calling method will
248      * block until this method returns.
249      * <p/>
250      * Note that the CacheManager calls this method from a synchronized method. Any attempt to
251      * call a synchronized method on CacheManager from this method will cause a deadlock.
252      * <p/>
253      * Note that a {@link net.sf.ehcache.event.CacheEventListener} status changed will also be triggered. Any
254      * attempt from that notification to access CacheManager will also result in a deadlock.
255      *
256      * @param cacheName the name of the <code>Cache</code> the operation relates to
257      */
258     public void notifyCacheRemoved(String cacheName) {
259 
260         ObjectName objectName = null;
261         try {
262             if (registerCaches) {
263                 objectName = Cache.createObjectName(backingCacheManager.toString(), cacheName);
264                 mBeanServer.unregisterMBean(objectName);
265             }
266             if (registerCacheConfigurations) {
267                 objectName = CacheConfiguration.createObjectName(backingCacheManager.toString(), cacheName);
268                 mBeanServer.unregisterMBean(objectName);
269             }
270             if (registerCacheStatistics) {
271                 objectName = CacheStatistics.createObjectName(backingCacheManager.toString(), cacheName);
272                 mBeanServer.unregisterMBean(objectName);
273             }
274         } catch (Exception e) {
275             LOG.error("Error unregistering cache for management for " + objectName
276                     + " . Error was " + e.getMessage(), e);
277         }
278 
279     }
280 
281 }