package org.apache.commons.jcs.admin; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.apache.commons.jcs.access.exception.CacheException; import org.apache.commons.jcs.auxiliary.remote.server.RemoteCacheServer; import org.apache.commons.jcs.auxiliary.remote.server.RemoteCacheServerFactory; import org.apache.commons.jcs.engine.CacheElementSerialized; import org.apache.commons.jcs.engine.behavior.ICacheElement; import org.apache.commons.jcs.engine.behavior.IElementAttributes; import org.apache.commons.jcs.engine.control.CompositeCache; import org.apache.commons.jcs.engine.control.CompositeCacheManager; import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.text.DateFormat; import java.util.Arrays; import java.util.Date; import java.util.LinkedList; import java.util.Set; /** * A servlet which provides HTTP access to JCS. Allows a summary of regions to be viewed, and * removeAll to be run on individual regions or all regions. Also provides the ability to remove * items (any number of key arguments can be provided with action 'remove'). Should be initialized * with a properties file that provides at least a classpath resource loader. */ public class JCSAdminBean implements JCSJMXBean { /** The cache manager. */ private final CompositeCacheManager cacheHub; /** * Default constructor */ public JCSAdminBean() { super(); try { this.cacheHub = CompositeCacheManager.getInstance(); } catch (CacheException e) { throw new RuntimeException("Could not retrieve cache manager instance", e); } } /** * Parameterized constructor * * @param cacheHub the cache manager instance */ public JCSAdminBean(CompositeCacheManager cacheHub) { super(); this.cacheHub = cacheHub; } /** * Builds up info about each element in a region. * <p> * @param cacheName * @return Array of CacheElementInfo objects * @throws Exception */ @Override public CacheElementInfo[] buildElementInfo( String cacheName ) throws Exception { CompositeCache<Serializable, Serializable> cache = cacheHub.getCache( cacheName ); Serializable[] keys = cache.getMemoryCache().getKeySet().toArray(new Serializable[0]); // Attempt to sort keys according to their natural ordering. If that // fails, get the key array again and continue unsorted. try { Arrays.sort( keys ); } catch ( Exception e ) { keys = cache.getMemoryCache().getKeySet().toArray(new Serializable[0]); } LinkedList<CacheElementInfo> records = new LinkedList<CacheElementInfo>(); ICacheElement<Serializable, Serializable> element; IElementAttributes attributes; CacheElementInfo elementInfo; DateFormat format = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT ); long now = System.currentTimeMillis(); for (Serializable key : keys) { element = cache.getMemoryCache().getQuiet( key ); attributes = element.getElementAttributes(); elementInfo = new CacheElementInfo( String.valueOf( key ), attributes.getIsEternal(), format.format(new Date(attributes.getCreateTime())), attributes.getMaxLife(), (now - attributes.getCreateTime() - attributes.getMaxLife() * 1000 ) / -1000); records.add( elementInfo ); } return records.toArray(new CacheElementInfo[0]); } /** * Builds up data on every region. * <p> * TODO we need a most light weight method that does not count bytes. The byte counting can * really swamp a server. * @return list of CacheRegionInfo objects * @throws Exception */ @Override public CacheRegionInfo[] buildCacheInfo() throws Exception { String[] cacheNames = cacheHub.getCacheNames(); Arrays.sort( cacheNames ); LinkedList<CacheRegionInfo> cacheInfo = new LinkedList<CacheRegionInfo>(); CacheRegionInfo regionInfo; CompositeCache<?, ?> cache; for ( int i = 0; i < cacheNames.length; i++ ) { cache = cacheHub.getCache( cacheNames[i] ); regionInfo = new CacheRegionInfo( cache.getCacheName(), cache.getSize(), cache.getStatus().toString(), cache.getStats(), cache.getHitCountRam(), cache.getHitCountAux(), cache.getMissCountNotFound(), cache.getMissCountExpired(), getByteCount( cache )); cacheInfo.add( regionInfo ); } return cacheInfo.toArray(new CacheRegionInfo[0]); } /** * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in * the region or an error occurs, suppresses exceptions and returns 0. * <p> * * @return int The size of the region in bytes. */ @Override public int getByteCount(String cacheName) { return getByteCount(cacheHub.getCache(cacheName)); } /** * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in * the region or an error occurs, suppresses exceptions and returns 0. * <p> * * @return int The size of the region in bytes. */ public <K, V> int getByteCount(CompositeCache<K, V> cache) { if (cache == null) { throw new IllegalArgumentException("The cache object specified was null."); } long size = 0; IMemoryCache<K, V> memCache = cache.getMemoryCache(); for (K key : memCache.getKeySet()) { ICacheElement<K, V> ice = null; try { ice = memCache.get(key); } catch (IOException e) { throw new RuntimeException("IOException while trying to get a cached element", e); } if (ice == null) { continue; } if (ice instanceof CacheElementSerialized) { size = size + ((CacheElementSerialized<K, V>) ice).getSerializedValue().length; } else { Object element = ice.getVal(); //CountingOnlyOutputStream: Keeps track of the number of bytes written to it, but doesn't write them anywhere. CountingOnlyOutputStream counter = new CountingOnlyOutputStream(); ObjectOutputStream out = null; try { out = new ObjectOutputStream(counter); out.writeObject(element); } catch (IOException e) { throw new RuntimeException("IOException while trying to measure the size of the cached element", e); } finally { try { if (out != null) { out.close(); } } catch (IOException e) { // ignore } try { counter.close(); } catch (IOException e) { // ignore } } // 4 bytes lost for the serialization header size = size + counter.getCount() - 4; } } if (size > Integer.MAX_VALUE) { throw new IllegalStateException("The size of cache " + cache.getCacheName() + " (" + size + " bytes) is too large to be represented as an integer."); } return (int) size; } /** * Clears all regions in the cache. * <p> * If this class is running within a remote cache server, clears all regions via the <code>RemoteCacheServer</code> * API, so that removes will be broadcast to client machines. Otherwise clears all regions in the cache directly via * the usual cache API. */ @Override public void clearAllRegions() throws IOException { if (RemoteCacheServerFactory.getRemoteCacheServer() == null) { // Not running in a remote cache server. // Remove objects from the cache directly, as no need to broadcast removes to client machines... String[] names = cacheHub.getCacheNames(); for (int i = 0; i < names.length; i++) { cacheHub.getCache(names[i]).removeAll(); } } else { // Running in a remote cache server. // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines... try { String[] cacheNames = cacheHub.getCacheNames(); // Call remoteCacheServer.removeAll(String) for each cacheName... RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer(); for (int i = 0; i < cacheNames.length; i++) { String cacheName = cacheNames[i]; remoteCacheServer.removeAll(cacheName); } } catch (IOException e) { throw new IllegalStateException("Failed to remove all elements from all cache regions: " + e, e); } } } /** * Clears a particular cache region. * <p> * If this class is running within a remote cache server, clears the region via the <code>RemoteCacheServer</code> * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual * cache API. */ @Override public void clearRegion(String cacheName) throws IOException { if (cacheName == null) { throw new IllegalArgumentException("The cache name specified was null."); } if (RemoteCacheServerFactory.getRemoteCacheServer() == null) { // Not running in a remote cache server. // Remove objects from the cache directly, as no need to broadcast removes to client machines... cacheHub.getCache(cacheName).removeAll(); } else { // Running in a remote cache server. // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines... try { // Call remoteCacheServer.removeAll(String)... RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer(); remoteCacheServer.removeAll(cacheName); } catch (IOException e) { throw new IllegalStateException("Failed to remove all elements from cache region [" + cacheName + "]: " + e, e); } } } /** * Removes a particular item from a particular region. * <p> * If this class is running within a remote cache server, removes the item via the <code>RemoteCacheServer</code> * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual * cache API. * * @param cacheName * @param key * * @throws IOException */ @Override public void removeItem(String cacheName, String key) throws IOException { if (cacheName == null) { throw new IllegalArgumentException("The cache name specified was null."); } if (key == null) { throw new IllegalArgumentException("The key specified was null."); } if (RemoteCacheServerFactory.getRemoteCacheServer() == null) { // Not running in a remote cache server. // Remove objects from the cache directly, as no need to broadcast removes to client machines... cacheHub.getCache(cacheName).remove(key); } else { // Running in a remote cache server. // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines... try { Object keyToRemove = null; CompositeCache<?, ?> cache = CompositeCacheManager.getInstance().getCache(cacheName); // A String key was supplied, but to remove elements via the RemoteCacheServer API, we need the // actual key object as stored in the cache (i.e. a Serializable object). To find the key in this form, // we iterate through all keys stored in the memory cache until we find one whose toString matches // the string supplied... Set<?> allKeysInCache = cache.getMemoryCache().getKeySet(); for (Object keyInCache : allKeysInCache) { if (keyInCache.toString().equals(key)) { if (keyToRemove == null) { keyToRemove = keyInCache; } else { // A key matching the one specified was already found... throw new IllegalStateException("Unexpectedly found duplicate keys in the cache region matching the key specified."); } } } if (keyToRemove == null) { throw new IllegalStateException("No match for this key could be found in the set of keys retrieved from the memory cache."); } // At this point, we have retrieved the matching K key. // Call remoteCacheServer.remove(String, Serializable)... RemoteCacheServer<Serializable, Serializable> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer(); remoteCacheServer.remove(cacheName, key); } catch (Exception e) { throw new IllegalStateException("Failed to remove element with key [" + key + ", " + key.getClass() + "] from cache region [" + cacheName + "]: " + e, e); } } } }