001package org.apache.commons.jcs3.admin; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.io.IOException; 023import java.io.ObjectOutputStream; 024import java.io.Serializable; 025import java.text.DateFormat; 026import java.util.Date; 027import java.util.LinkedList; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031import java.util.TreeMap; 032import java.util.TreeSet; 033import java.util.stream.Collectors; 034 035import org.apache.commons.jcs3.access.exception.CacheException; 036import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServer; 037import org.apache.commons.jcs3.auxiliary.remote.server.RemoteCacheServerFactory; 038import org.apache.commons.jcs3.engine.CacheElementSerialized; 039import org.apache.commons.jcs3.engine.behavior.ICacheElement; 040import org.apache.commons.jcs3.engine.behavior.IElementAttributes; 041import org.apache.commons.jcs3.engine.control.CompositeCache; 042import org.apache.commons.jcs3.engine.control.CompositeCacheManager; 043import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache; 044 045/** 046 * A servlet which provides HTTP access to JCS. Allows a summary of regions to be viewed, and 047 * removeAll to be run on individual regions or all regions. Also provides the ability to remove 048 * items (any number of key arguments can be provided with action 'remove'). Should be initialized 049 * with a properties file that provides at least a classpath resource loader. 050 */ 051public class JCSAdminBean implements JCSJMXBean 052{ 053 /** The cache manager. */ 054 private final CompositeCacheManager cacheHub; 055 056 /** 057 * Default constructor 058 */ 059 public JCSAdminBean() 060 { 061 try 062 { 063 this.cacheHub = CompositeCacheManager.getInstance(); 064 } 065 catch (final CacheException e) 066 { 067 throw new RuntimeException("Could not retrieve cache manager instance", e); 068 } 069 } 070 071 /** 072 * Parameterized constructor 073 * 074 * @param cacheHub the cache manager instance 075 */ 076 public JCSAdminBean(final CompositeCacheManager cacheHub) 077 { 078 this.cacheHub = cacheHub; 079 } 080 081 /** 082 * Builds up info about each element in a region. 083 * <p> 084 * @param cacheName 085 * @return List of CacheElementInfo objects 086 * @throws IOException 087 */ 088 @Override 089 public List<CacheElementInfo> buildElementInfo( final String cacheName ) 090 throws IOException 091 { 092 final CompositeCache<Object, Object> cache = cacheHub.getCache( cacheName ); 093 094 // Convert all keys to string, store in a sorted map 095 final TreeMap<String, ?> keys = new TreeMap<>(cache.getMemoryCache().getKeySet() 096 .stream() 097 .collect(Collectors.toMap(Object::toString, k -> k))); 098 099 final LinkedList<CacheElementInfo> records = new LinkedList<>(); 100 101 final DateFormat format = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT ); 102 103 final long now = System.currentTimeMillis(); 104 105 for (final Map.Entry<String, ?> key : keys.entrySet()) 106 { 107 final ICacheElement<?, ?> element = cache.getMemoryCache().getQuiet( key.getValue() ); 108 109 final IElementAttributes attributes = element.getElementAttributes(); 110 111 final CacheElementInfo elementInfo = new CacheElementInfo( 112 key.getKey(), 113 attributes.getIsEternal(), 114 format.format(new Date(attributes.getCreateTime())), 115 attributes.getMaxLife(), 116 (now - attributes.getCreateTime() - attributes.getMaxLife() * 1000 ) / -1000); 117 118 records.add( elementInfo ); 119 } 120 121 return records; 122 } 123 124 /** 125 * Builds up data on every region. 126 * <p> 127 * TODO we need a most light weight method that does not count bytes. The byte counting can 128 * really swamp a server. 129 * @return List of CacheRegionInfo objects 130 */ 131 @Override 132 public List<CacheRegionInfo> buildCacheInfo() 133 { 134 final TreeSet<String> cacheNames = new TreeSet<>(cacheHub.getCacheNames()); 135 136 final LinkedList<CacheRegionInfo> cacheInfo = new LinkedList<>(); 137 138 for (final String cacheName : cacheNames) 139 { 140 final CompositeCache<?, ?> cache = cacheHub.getCache( cacheName ); 141 142 final CacheRegionInfo regionInfo = new CacheRegionInfo( 143 cache.getCacheName(), 144 cache.getSize(), 145 cache.getStatus().toString(), 146 cache.getStats(), 147 cache.getHitCountRam(), 148 cache.getHitCountAux(), 149 cache.getMissCountNotFound(), 150 cache.getMissCountExpired(), 151 getByteCount( cache )); 152 153 cacheInfo.add( regionInfo ); 154 } 155 156 return cacheInfo; 157 } 158 159 160 /** 161 * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in 162 * the region or an error occurs, suppresses exceptions and returns 0. 163 * <p> 164 * 165 * @return int The size of the region in bytes. 166 */ 167 @Override 168 public long getByteCount(final String cacheName) 169 { 170 return getByteCount(cacheHub.getCache(cacheName)); 171 } 172 173 /** 174 * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in 175 * the region or an error occurs, suppresses exceptions and returns 0. 176 * <p> 177 * 178 * @return int The size of the region in bytes. 179 */ 180 public <K, V> long getByteCount(final CompositeCache<K, V> cache) 181 { 182 if (cache == null) 183 { 184 throw new IllegalArgumentException("The cache object specified was null."); 185 } 186 187 long size = 0; 188 final IMemoryCache<K, V> memCache = cache.getMemoryCache(); 189 190 for (final K key : memCache.getKeySet()) 191 { 192 ICacheElement<K, V> ice = null; 193 try 194 { 195 ice = memCache.get(key); 196 } 197 catch (final IOException e) 198 { 199 throw new RuntimeException("IOException while trying to get a cached element", e); 200 } 201 202 if (ice == null) 203 { 204 continue; 205 } 206 207 if (ice instanceof CacheElementSerialized) 208 { 209 size += ((CacheElementSerialized<K, V>) ice).getSerializedValue().length; 210 } 211 else 212 { 213 final Object element = ice.getVal(); 214 215 //CountingOnlyOutputStream: Keeps track of the number of bytes written to it, but doesn't write them anywhere. 216 final CountingOnlyOutputStream counter = new CountingOnlyOutputStream(); 217 try (ObjectOutputStream out = new ObjectOutputStream(counter)) 218 { 219 out.writeObject(element); 220 } 221 catch (final IOException e) 222 { 223 throw new RuntimeException("IOException while trying to measure the size of the cached element", e); 224 } 225 finally 226 { 227 try 228 { 229 counter.close(); 230 } 231 catch (final IOException e) 232 { 233 // ignore 234 } 235 } 236 237 // 4 bytes lost for the serialization header 238 size += counter.getCount() - 4; 239 } 240 } 241 242 return size; 243 } 244 245 /** 246 * Clears all regions in the cache. 247 * <p> 248 * If this class is running within a remote cache server, clears all regions via the <code>RemoteCacheServer</code> 249 * API, so that removes will be broadcast to client machines. Otherwise clears all regions in the cache directly via 250 * the usual cache API. 251 */ 252 @Override 253 public void clearAllRegions() throws IOException 254 { 255 final RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer(); 256 257 if (remoteCacheServer == null) 258 { 259 // Not running in a remote cache server. 260 // Remove objects from the cache directly, as no need to broadcast removes to client machines... 261 for (final String name : cacheHub.getCacheNames()) 262 { 263 cacheHub.getCache(name).removeAll(); 264 } 265 } 266 else 267 { 268 // Running in a remote cache server. 269 // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines... 270 // Call remoteCacheServer.removeAll(String) for each cacheName... 271 for (final String name : cacheHub.getCacheNames()) 272 { 273 remoteCacheServer.removeAll(name); 274 } 275 } 276 } 277 278 /** 279 * Clears a particular cache region. 280 * <p> 281 * If this class is running within a remote cache server, clears the region via the <code>RemoteCacheServer</code> 282 * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual 283 * cache API. 284 */ 285 @Override 286 public void clearRegion(final String cacheName) throws IOException 287 { 288 if (cacheName == null) 289 { 290 throw new IllegalArgumentException("The cache name specified was null."); 291 } 292 if (RemoteCacheServerFactory.getRemoteCacheServer() == null) 293 { 294 // Not running in a remote cache server. 295 // Remove objects from the cache directly, as no need to broadcast removes to client machines... 296 cacheHub.getCache(cacheName).removeAll(); 297 } 298 else 299 { 300 // Running in a remote cache server. 301 // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines... 302 try 303 { 304 // Call remoteCacheServer.removeAll(String)... 305 final RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer(); 306 remoteCacheServer.removeAll(cacheName); 307 } 308 catch (final IOException e) 309 { 310 throw new IllegalStateException("Failed to remove all elements from cache region [" + cacheName + "]: " + e, e); 311 } 312 } 313 } 314 315 /** 316 * Removes a particular item from a particular region. 317 * <p> 318 * If this class is running within a remote cache server, removes the item via the <code>RemoteCacheServer</code> 319 * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual 320 * cache API. 321 * 322 * @param cacheName 323 * @param key 324 * 325 * @throws IOException 326 */ 327 @Override 328 public void removeItem(final String cacheName, final String key) throws IOException 329 { 330 if (cacheName == null) 331 { 332 throw new IllegalArgumentException("The cache name specified was null."); 333 } 334 if (key == null) 335 { 336 throw new IllegalArgumentException("The key specified was null."); 337 } 338 if (RemoteCacheServerFactory.getRemoteCacheServer() == null) 339 { 340 // Not running in a remote cache server. 341 // Remove objects from the cache directly, as no need to broadcast removes to client machines... 342 cacheHub.getCache(cacheName).remove(key); 343 } 344 else 345 { 346 // Running in a remote cache server. 347 // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines... 348 try 349 { 350 Object keyToRemove = null; 351 final CompositeCache<?, ?> cache = CompositeCacheManager.getInstance().getCache(cacheName); 352 353 // A String key was supplied, but to remove elements via the RemoteCacheServer API, we need the 354 // actual key object as stored in the cache (i.e. a Serializable object). To find the key in this form, 355 // we iterate through all keys stored in the memory cache until we find one whose toString matches 356 // the string supplied... 357 final Set<?> allKeysInCache = cache.getMemoryCache().getKeySet(); 358 for (final Object keyInCache : allKeysInCache) 359 { 360 if (keyInCache.toString().equals(key)) 361 { 362 if (keyToRemove != null) { 363 // A key matching the one specified was already found... 364 throw new IllegalStateException("Unexpectedly found duplicate keys in the cache region matching the key specified."); 365 } 366 keyToRemove = keyInCache; 367 } 368 } 369 if (keyToRemove == null) 370 { 371 throw new IllegalStateException("No match for this key could be found in the set of keys retrieved from the memory cache."); 372 } 373 // At this point, we have retrieved the matching K key. 374 375 // Call remoteCacheServer.remove(String, Serializable)... 376 final RemoteCacheServer<Serializable, Serializable> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer(); 377 remoteCacheServer.remove(cacheName, key); 378 } 379 catch (final Exception e) 380 { 381 throw new IllegalStateException("Failed to remove element with key [" + key + ", " + key.getClass() + "] from cache region [" + cacheName + "]: " + e, e); 382 } 383 } 384 } 385}