| 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(); |
| try (ObjectOutputStream 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 |
| { |
| 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); |
| } |
| } |
| } |
| } |