001package org.apache.commons.jcs.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 org.apache.commons.jcs.access.exception.CacheException; 023import org.apache.commons.jcs.auxiliary.remote.server.RemoteCacheServer; 024import org.apache.commons.jcs.auxiliary.remote.server.RemoteCacheServerFactory; 025import org.apache.commons.jcs.engine.CacheElementSerialized; 026import org.apache.commons.jcs.engine.behavior.ICacheElement; 027import org.apache.commons.jcs.engine.behavior.IElementAttributes; 028import org.apache.commons.jcs.engine.control.CompositeCache; 029import org.apache.commons.jcs.engine.control.CompositeCacheManager; 030import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache; 031 032import java.io.IOException; 033import java.io.ObjectOutputStream; 034import java.io.Serializable; 035import java.text.DateFormat; 036import java.util.Arrays; 037import java.util.Date; 038import java.util.LinkedList; 039import java.util.Set; 040 041/** 042 * A servlet which provides HTTP access to JCS. Allows a summary of regions to be viewed, and 043 * removeAll to be run on individual regions or all regions. Also provides the ability to remove 044 * items (any number of key arguments can be provided with action 'remove'). Should be initialized 045 * with a properties file that provides at least a classpath resource loader. 046 */ 047public class JCSAdminBean implements JCSJMXBean 048{ 049 /** The cache manager. */ 050 private final CompositeCacheManager cacheHub; 051 052 /** 053 * Default constructor 054 */ 055 public JCSAdminBean() 056 { 057 super(); 058 try 059 { 060 this.cacheHub = CompositeCacheManager.getInstance(); 061 } 062 catch (CacheException e) 063 { 064 throw new RuntimeException("Could not retrieve cache manager instance", e); 065 } 066 } 067 068 /** 069 * Parameterized constructor 070 * 071 * @param cacheHub the cache manager instance 072 */ 073 public JCSAdminBean(CompositeCacheManager cacheHub) 074 { 075 super(); 076 this.cacheHub = cacheHub; 077 } 078 079 /** 080 * Builds up info about each element in a region. 081 * <p> 082 * @param cacheName 083 * @return Array of CacheElementInfo objects 084 * @throws Exception 085 */ 086 @Override 087 public CacheElementInfo[] buildElementInfo( String cacheName ) 088 throws Exception 089 { 090 CompositeCache<Serializable, Serializable> cache = cacheHub.getCache( cacheName ); 091 092 Serializable[] keys = cache.getMemoryCache().getKeySet().toArray(new Serializable[0]); 093 094 // Attempt to sort keys according to their natural ordering. If that 095 // fails, get the key array again and continue unsorted. 096 try 097 { 098 Arrays.sort( keys ); 099 } 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}