View Javadoc
1   package org.apache.commons.jcs.admin;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.commons.jcs.access.exception.CacheException;
23  import org.apache.commons.jcs.auxiliary.remote.server.RemoteCacheServer;
24  import org.apache.commons.jcs.auxiliary.remote.server.RemoteCacheServerFactory;
25  import org.apache.commons.jcs.engine.CacheElementSerialized;
26  import org.apache.commons.jcs.engine.behavior.ICacheElement;
27  import org.apache.commons.jcs.engine.behavior.IElementAttributes;
28  import org.apache.commons.jcs.engine.control.CompositeCache;
29  import org.apache.commons.jcs.engine.control.CompositeCacheManager;
30  import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache;
31  
32  import java.io.IOException;
33  import java.io.ObjectOutputStream;
34  import java.io.Serializable;
35  import java.text.DateFormat;
36  import java.util.Arrays;
37  import java.util.Date;
38  import java.util.LinkedList;
39  import java.util.Set;
40  
41  /**
42   * A servlet which provides HTTP access to JCS. Allows a summary of regions to be viewed, and
43   * removeAll to be run on individual regions or all regions. Also provides the ability to remove
44   * items (any number of key arguments can be provided with action 'remove'). Should be initialized
45   * with a properties file that provides at least a classpath resource loader.
46   */
47  public class JCSAdminBean implements JCSJMXBean
48  {
49      /** The cache manager. */
50      private final CompositeCacheManager cacheHub;
51  
52      /**
53       * Default constructor
54       */
55      public JCSAdminBean()
56      {
57          super();
58          try
59          {
60              this.cacheHub = CompositeCacheManager.getInstance();
61          }
62          catch (CacheException e)
63          {
64              throw new RuntimeException("Could not retrieve cache manager instance", e);
65          }
66      }
67  
68      /**
69       * Parameterized constructor
70       *
71  	 * @param cacheHub the cache manager instance
72  	 */
73  	public JCSAdminBean(CompositeCacheManager cacheHub)
74  	{
75  		super();
76  		this.cacheHub = cacheHub;
77  	}
78  
79  	/**
80       * Builds up info about each element in a region.
81       * <p>
82       * @param cacheName
83       * @return Array of CacheElementInfo objects
84       * @throws Exception
85       */
86      @Override
87      public CacheElementInfo[] buildElementInfo( String cacheName )
88          throws Exception
89      {
90          CompositeCache<Serializable, Serializable> cache = cacheHub.getCache( cacheName );
91  
92          Serializable[] keys = cache.getMemoryCache().getKeySet().toArray(new Serializable[0]);
93  
94          // Attempt to sort keys according to their natural ordering. If that
95          // fails, get the key array again and continue unsorted.
96          try
97          {
98              Arrays.sort( keys );
99          }
100         catch ( Exception e )
101         {
102             keys = cache.getMemoryCache().getKeySet().toArray(new Serializable[0]);
103         }
104 
105         LinkedList<CacheElementInfo> records = new LinkedList<CacheElementInfo>();
106 
107         ICacheElement<Serializable, Serializable> element;
108         IElementAttributes attributes;
109         CacheElementInfo elementInfo;
110 
111         DateFormat format = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT );
112 
113         long now = System.currentTimeMillis();
114 
115         for (Serializable key : keys)
116         {
117             element = cache.getMemoryCache().getQuiet( key );
118 
119             attributes = element.getElementAttributes();
120 
121             elementInfo = new CacheElementInfo(
122             		String.valueOf( key ),
123             		attributes.getIsEternal(),
124             		format.format(new Date(attributes.getCreateTime())),
125             		attributes.getMaxLife(),
126             		(now - attributes.getCreateTime() - attributes.getMaxLife() * 1000 ) / -1000);
127 
128             records.add( elementInfo );
129         }
130 
131         return records.toArray(new CacheElementInfo[0]);
132     }
133 
134     /**
135      * Builds up data on every region.
136      * <p>
137      * TODO we need a most light weight method that does not count bytes. The byte counting can
138      *       really swamp a server.
139      * @return list of CacheRegionInfo objects
140      * @throws Exception
141      */
142     @Override
143     public CacheRegionInfo[] buildCacheInfo()
144         throws Exception
145     {
146         String[] cacheNames = cacheHub.getCacheNames();
147 
148         Arrays.sort( cacheNames );
149 
150         LinkedList<CacheRegionInfo> cacheInfo = new LinkedList<CacheRegionInfo>();
151 
152         CacheRegionInfo regionInfo;
153         CompositeCache<?, ?> cache;
154 
155         for ( int i = 0; i < cacheNames.length; i++ )
156         {
157             cache = cacheHub.getCache( cacheNames[i] );
158 
159             regionInfo = new CacheRegionInfo(
160                     cache.getCacheName(),
161                     cache.getSize(),
162                     cache.getStatus().toString(),
163                     cache.getStats(),
164                     cache.getHitCountRam(),
165                     cache.getHitCountAux(),
166                     cache.getMissCountNotFound(),
167                     cache.getMissCountExpired(),
168                     getByteCount( cache ));
169 
170             cacheInfo.add( regionInfo );
171         }
172 
173         return cacheInfo.toArray(new CacheRegionInfo[0]);
174     }
175 
176 
177 	/**
178      * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in
179      * the region or an error occurs, suppresses exceptions and returns 0.
180      * <p>
181      *
182      * @return int The size of the region in bytes.
183      */
184 	@Override
185     public int getByteCount(String cacheName)
186 	{
187 		return getByteCount(cacheHub.getCache(cacheName));
188 	}
189 
190 	/**
191      * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in
192      * the region or an error occurs, suppresses exceptions and returns 0.
193      * <p>
194      *
195      * @return int The size of the region in bytes.
196      */
197     public <K, V> int getByteCount(CompositeCache<K, V> cache)
198     {
199         if (cache == null)
200         {
201             throw new IllegalArgumentException("The cache object specified was null.");
202         }
203 
204         long size = 0;
205         IMemoryCache<K, V> memCache = cache.getMemoryCache();
206 
207         for (K key : memCache.getKeySet())
208         {
209             ICacheElement<K, V> ice = null;
210 			try
211 			{
212 				ice = memCache.get(key);
213 			}
214 			catch (IOException e)
215 			{
216                 throw new RuntimeException("IOException while trying to get a cached element", e);
217 			}
218 
219 			if (ice == null)
220 			{
221 				continue;
222 			}
223 
224 			if (ice instanceof CacheElementSerialized)
225             {
226                 size = size + ((CacheElementSerialized<K, V>) ice).getSerializedValue().length;
227             }
228             else
229             {
230                 Object element = ice.getVal();
231 
232                 //CountingOnlyOutputStream: Keeps track of the number of bytes written to it, but doesn't write them anywhere.
233                 CountingOnlyOutputStream counter = new CountingOnlyOutputStream();
234                 ObjectOutputStream out = null;
235                 try
236                 {
237                     out = new ObjectOutputStream(counter);
238                     out.writeObject(element);
239                 }
240                 catch (IOException e)
241                 {
242                     throw new RuntimeException("IOException while trying to measure the size of the cached element", e);
243                 }
244                 finally
245                 {
246                 	try
247                 	{
248                 		if (out != null)
249                 		{
250                 			out.close();
251                 		}
252 					}
253                 	catch (IOException e)
254                 	{
255                 		// ignore
256 					}
257                 	try
258                 	{
259 						counter.close();
260 					}
261                 	catch (IOException e)
262                 	{
263                 		// ignore
264 					}
265                 }
266 
267                 // 4 bytes lost for the serialization header
268                 size = size + counter.getCount() - 4;
269             }
270         }
271 
272         if (size > Integer.MAX_VALUE)
273         {
274             throw new IllegalStateException("The size of cache " + cache.getCacheName() + " (" + size + " bytes) is too large to be represented as an integer.");
275         }
276 
277         return (int) size;
278     }
279 
280     /**
281      * Clears all regions in the cache.
282      * <p>
283      * If this class is running within a remote cache server, clears all regions via the <code>RemoteCacheServer</code>
284      * API, so that removes will be broadcast to client machines. Otherwise clears all regions in the cache directly via
285      * the usual cache API.
286      */
287     @Override
288     public void clearAllRegions() throws IOException
289     {
290         if (RemoteCacheServerFactory.getRemoteCacheServer() == null)
291         {
292             // Not running in a remote cache server.
293             // Remove objects from the cache directly, as no need to broadcast removes to client machines...
294 
295             String[] names = cacheHub.getCacheNames();
296 
297             for (int i = 0; i < names.length; i++)
298             {
299                 cacheHub.getCache(names[i]).removeAll();
300             }
301         }
302         else
303         {
304             // Running in a remote cache server.
305             // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
306             try
307             {
308                 String[] cacheNames = cacheHub.getCacheNames();
309 
310                 // Call remoteCacheServer.removeAll(String) for each cacheName...
311                 RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
312                 for (int i = 0; i < cacheNames.length; i++)
313                 {
314                     String cacheName = cacheNames[i];
315                     remoteCacheServer.removeAll(cacheName);
316                 }
317             }
318             catch (IOException e)
319             {
320                 throw new IllegalStateException("Failed to remove all elements from all cache regions: " + e, e);
321             }
322         }
323     }
324 
325     /**
326      * Clears a particular cache region.
327      * <p>
328      * If this class is running within a remote cache server, clears the region via the <code>RemoteCacheServer</code>
329      * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
330      * cache API.
331      */
332     @Override
333     public void clearRegion(String cacheName) throws IOException
334     {
335         if (cacheName == null)
336         {
337             throw new IllegalArgumentException("The cache name specified was null.");
338         }
339         if (RemoteCacheServerFactory.getRemoteCacheServer() == null)
340         {
341             // Not running in a remote cache server.
342             // Remove objects from the cache directly, as no need to broadcast removes to client machines...
343             cacheHub.getCache(cacheName).removeAll();
344         }
345         else
346         {
347             // Running in a remote cache server.
348             // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
349             try
350             {
351                 // Call remoteCacheServer.removeAll(String)...
352                 RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
353                 remoteCacheServer.removeAll(cacheName);
354             }
355             catch (IOException e)
356             {
357                 throw new IllegalStateException("Failed to remove all elements from cache region [" + cacheName + "]: " + e, e);
358             }
359         }
360     }
361 
362     /**
363      * Removes a particular item from a particular region.
364      * <p>
365      * If this class is running within a remote cache server, removes the item via the <code>RemoteCacheServer</code>
366      * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
367      * cache API.
368      *
369      * @param cacheName
370      * @param key
371      *
372      * @throws IOException
373      */
374     @Override
375     public void removeItem(String cacheName, String key) throws IOException
376     {
377         if (cacheName == null)
378         {
379             throw new IllegalArgumentException("The cache name specified was null.");
380         }
381         if (key == null)
382         {
383             throw new IllegalArgumentException("The key specified was null.");
384         }
385         if (RemoteCacheServerFactory.getRemoteCacheServer() == null)
386         {
387             // Not running in a remote cache server.
388             // Remove objects from the cache directly, as no need to broadcast removes to client machines...
389             cacheHub.getCache(cacheName).remove(key);
390         }
391         else
392         {
393             // Running in a remote cache server.
394             // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
395             try
396             {
397                 Object keyToRemove = null;
398                 CompositeCache<?, ?> cache = CompositeCacheManager.getInstance().getCache(cacheName);
399 
400                 // A String key was supplied, but to remove elements via the RemoteCacheServer API, we need the
401                 // actual key object as stored in the cache (i.e. a Serializable object). To find the key in this form,
402                 // we iterate through all keys stored in the memory cache until we find one whose toString matches
403                 // the string supplied...
404                 Set<?> allKeysInCache = cache.getMemoryCache().getKeySet();
405                 for (Object keyInCache : allKeysInCache)
406                 {
407                     if (keyInCache.toString().equals(key))
408                     {
409                         if (keyToRemove == null)
410                         {
411                             keyToRemove = keyInCache;
412                         }
413                         else
414                         {
415                             // A key matching the one specified was already found...
416                             throw new IllegalStateException("Unexpectedly found duplicate keys in the cache region matching the key specified.");
417                         }
418                     }
419                 }
420                 if (keyToRemove == null)
421                 {
422                     throw new IllegalStateException("No match for this key could be found in the set of keys retrieved from the memory cache.");
423                 }
424                 // At this point, we have retrieved the matching K key.
425 
426                 // Call remoteCacheServer.remove(String, Serializable)...
427                 RemoteCacheServer<Serializable, Serializable> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
428                 remoteCacheServer.remove(cacheName, key);
429             }
430             catch (Exception e)
431             {
432                 throw new IllegalStateException("Failed to remove element with key [" + key + ", " + key.getClass() + "] from cache region [" + cacheName + "]: " + e, e);
433             }
434         }
435     }
436 }