001 package org.apache.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
022 import java.io.IOException;
023 import java.io.ObjectOutputStream;
024 import java.io.Serializable;
025 import java.text.DateFormat;
026 import java.util.Arrays;
027 import java.util.Date;
028 import java.util.Iterator;
029 import java.util.LinkedList;
030 import java.util.Map;
031 import java.util.Set;
032
033 import org.apache.jcs.access.exception.CacheException;
034 import org.apache.jcs.auxiliary.remote.server.RemoteCacheServer;
035 import org.apache.jcs.auxiliary.remote.server.RemoteCacheServerFactory;
036 import org.apache.jcs.engine.CacheElementSerialized;
037 import org.apache.jcs.engine.behavior.ICacheElement;
038 import org.apache.jcs.engine.behavior.IElementAttributes;
039 import org.apache.jcs.engine.control.CompositeCache;
040 import org.apache.jcs.engine.control.CompositeCacheManager;
041 import org.apache.jcs.engine.memory.behavior.IMemoryCache;
042 import org.apache.jcs.engine.memory.util.MemoryElementDescriptor;
043
044 /**
045 * A servlet which provides HTTP access to JCS. Allows a summary of regions to be viewed, and
046 * removeAll to be run on individual regions or all regions. Also provides the ability to remove
047 * items (any number of key arguments can be provided with action 'remove'). Should be initialized
048 * with a properties file that provides at least a classpath resource loader.
049 */
050 public class JCSAdminBean
051 {
052 /** The cache manager. */
053 private final CompositeCacheManager cacheHub;
054
055 /**
056 * Default constructor
057 */
058 public JCSAdminBean()
059 {
060 super();
061 try
062 {
063 this.cacheHub = CompositeCacheManager.getInstance();
064 }
065 catch (CacheException e)
066 {
067 throw new RuntimeException("Could not retrieve cache manager instance", e);
068 }
069 }
070
071 /**
072 * Builds up info about each element in a region.
073 * <p>
074 * @param cacheName
075 * @return List of CacheElementInfo objects
076 * @throws Exception
077 */
078 public LinkedList<CacheElementInfo> buildElementInfo( String cacheName )
079 throws Exception
080 {
081 CompositeCache<Serializable, Serializable> cache = cacheHub.getCache( cacheName );
082
083 Serializable[] keys = cache.getMemoryCache().getKeySet().toArray(new Serializable[0]);
084
085 // Attempt to sort keys according to their natural ordering. If that
086 // fails, get the key array again and continue unsorted.
087 try
088 {
089 Arrays.sort( keys );
090 }
091 catch ( Exception e )
092 {
093 keys = cache.getMemoryCache().getKeySet().toArray(new Serializable[0]);
094 }
095
096 LinkedList<CacheElementInfo> records = new LinkedList<CacheElementInfo>();
097
098 ICacheElement<Serializable, Serializable> element;
099 IElementAttributes attributes;
100 CacheElementInfo elementInfo;
101
102 DateFormat format = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT );
103
104 long now = System.currentTimeMillis();
105
106 for (Serializable key : keys)
107 {
108 element = cache.getMemoryCache().getQuiet( key );
109
110 attributes = element.getElementAttributes();
111
112 elementInfo = new CacheElementInfo();
113
114 elementInfo.key = String.valueOf( key );
115 elementInfo.eternal = attributes.getIsEternal();
116 elementInfo.maxLifeSeconds = attributes.getMaxLifeSeconds();
117
118 elementInfo.createTime = format.format( new Date( attributes.getCreateTime() ) );
119
120 elementInfo.expiresInSeconds = ( now - attributes.getCreateTime() - ( attributes.getMaxLifeSeconds() * 1000 ) )
121 / -1000;
122
123 records.add( elementInfo );
124 }
125
126 return records;
127 }
128
129 /**
130 * Builds up data on every region.
131 * <p>
132 * @TODO we need a most light weight method that does not count bytes. The byte counting can
133 * really swamp a server.
134 * @return list of CacheRegionInfo objects
135 * @throws Exception
136 */
137 public LinkedList<CacheRegionInfo> buildCacheInfo()
138 throws Exception
139 {
140 String[] cacheNames = cacheHub.getCacheNames();
141
142 Arrays.sort( cacheNames );
143
144 LinkedList<CacheRegionInfo> cacheInfo = new LinkedList<CacheRegionInfo>();
145
146 CacheRegionInfo regionInfo;
147 CompositeCache<?, ?> cache;
148
149 for ( int i = 0; i < cacheNames.length; i++ )
150 {
151 cache = cacheHub.getCache( cacheNames[i] );
152
153 regionInfo = new CacheRegionInfo();
154
155 regionInfo.cache = cache;
156 regionInfo.byteCount = getByteCount( cache );
157
158 cacheInfo.add( regionInfo );
159 }
160
161 return cacheInfo;
162 }
163
164 /**
165 * Tries to estimate how much data is in a region. This is expensive. If there are any non serializable objects in
166 * the region or an error occurs, suppresses exceptions and returns 0.
167 * <p/>
168 *
169 * @return int The size of the region in bytes.
170 */
171 public <K extends Serializable, V extends Serializable> int getByteCount(CompositeCache<K, V> cache)
172 {
173 if (cache == null)
174 {
175 throw new IllegalArgumentException("The cache object specified was null.");
176 }
177
178 long size = 0;
179 IMemoryCache<K, V> memCache = cache.getMemoryCache();
180
181 Iterator<Map.Entry<K, MemoryElementDescriptor<K, V>>> iter = memCache.getIterator();
182 while (iter.hasNext())
183 {
184 MemoryElementDescriptor<K, V> me = iter.next().getValue();
185 ICacheElement<K, V> ice = me.ce;
186
187 if (ice instanceof CacheElementSerialized)
188 {
189 size = size + ((CacheElementSerialized<K, V>) ice).getSerializedValue().length;
190 }
191 else
192 {
193 Serializable element = ice.getVal();
194
195 //CountingOnlyOutputStream: Keeps track of the number of bytes written to it, but doesn't write them anywhere.
196 CountingOnlyOutputStream counter = new CountingOnlyOutputStream();
197 try
198 {
199 ObjectOutputStream out = new ObjectOutputStream(counter);
200 out.writeObject(element);
201 }
202 catch (IOException e)
203 {
204 throw new RuntimeException("IOException while trying to measure the size of the cached element", e);
205 }
206
207 // 4 bytes lost for the serialization header
208 size = size + counter.getCount() - 4;
209 }
210 }
211
212 if (size > Integer.MAX_VALUE)
213 {
214 throw new IllegalStateException("The size of cache " + cache.getCacheName() + " (" + size + " bytes) is too large to be represented as an integer.");
215 }
216
217 return (int) size;
218 }
219
220 /**
221 * Clears all regions in the cache.
222 * <p/>
223 * If this class is running within a remote cache server, clears all regions via the <code>RemoteCacheServer</code>
224 * API, so that removes will be broadcast to client machines. Otherwise clears all regions in the cache directly via
225 * the usual cache API.
226 */
227 public void clearAllRegions() throws IOException
228 {
229 if (RemoteCacheServerFactory.getRemoteCacheServer() == null)
230 {
231 // Not running in a remote cache server.
232 // Remove objects from the cache directly, as no need to broadcast removes to client machines...
233
234 String[] names = cacheHub.getCacheNames();
235
236 for (int i = 0; i < names.length; i++)
237 {
238 cacheHub.getCache(names[i]).removeAll();
239 }
240 }
241 else
242 {
243 // Running in a remote cache server.
244 // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
245 try
246 {
247 String[] cacheNames = cacheHub.getCacheNames();
248
249 // Call remoteCacheServer.removeAll(String) for each cacheName...
250 RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
251 for (int i = 0; i < cacheNames.length; i++)
252 {
253 String cacheName = cacheNames[i];
254 remoteCacheServer.removeAll(cacheName);
255 }
256 }
257 catch (IOException e)
258 {
259 throw new IllegalStateException("Failed to remove all elements from all cache regions: " + e, e);
260 }
261 }
262 }
263
264 /**
265 * Clears a particular cache region.
266 * <p/>
267 * If this class is running within a remote cache server, clears the region via the <code>RemoteCacheServer</code>
268 * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
269 * cache API.
270 */
271 public void clearRegion(String cacheName) throws IOException
272 {
273 if (cacheName == null)
274 {
275 throw new IllegalArgumentException("The cache name specified was null.");
276 }
277 if (RemoteCacheServerFactory.getRemoteCacheServer() == null)
278 {
279 // Not running in a remote cache server.
280 // Remove objects from the cache directly, as no need to broadcast removes to client machines...
281 cacheHub.getCache(cacheName).removeAll();
282 }
283 else
284 {
285 // Running in a remote cache server.
286 // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
287 try
288 {
289 // Call remoteCacheServer.removeAll(String)...
290 RemoteCacheServer<?, ?> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
291 remoteCacheServer.removeAll(cacheName);
292 }
293 catch (IOException e)
294 {
295 throw new IllegalStateException("Failed to remove all elements from cache region [" + cacheName + "]: " + e, e);
296 }
297 }
298 }
299
300 /**
301 * Removes a particular item from a particular region.
302 * <p/>
303 * If this class is running within a remote cache server, removes the item via the <code>RemoteCacheServer</code>
304 * API, so that removes will be broadcast to client machines. Otherwise clears the region directly via the usual
305 * cache API.
306 *
307 * @param cacheName
308 * @param key
309 *
310 * @throws IOException
311 */
312 public void removeItem(String cacheName, String key) throws IOException
313 {
314 if (cacheName == null)
315 {
316 throw new IllegalArgumentException("The cache name specified was null.");
317 }
318 if (key == null)
319 {
320 throw new IllegalArgumentException("The key specified was null.");
321 }
322 if (RemoteCacheServerFactory.getRemoteCacheServer() == null)
323 {
324 // Not running in a remote cache server.
325 // Remove objects from the cache directly, as no need to broadcast removes to client machines...
326 cacheHub.getCache(cacheName).remove(key);
327 }
328 else
329 {
330 // Running in a remote cache server.
331 // Remove objects via the RemoteCacheServer API, so that removes will be broadcast to client machines...
332 try
333 {
334 Serializable keyToRemove = null;
335 CompositeCache<? extends Serializable, ? extends Serializable> cache = CompositeCacheManager.getInstance().getCache(cacheName);
336
337 // A String key was supplied, but to remove elements via the RemoteCacheServer API, we need the
338 // actual key object as stored in the cache (i.e. a Serializable object). To find the key in this form,
339 // we iterate through all keys stored in the memory cache until we find one whose toString matches
340 // the string supplied...
341 Set<? extends Serializable> allKeysInCache = cache.getMemoryCache().getKeySet();
342 for (Serializable keyInCache : allKeysInCache)
343 {
344 if (keyInCache.toString().equals(key))
345 {
346 if (keyToRemove == null)
347 {
348 keyToRemove = keyInCache;
349 }
350 else
351 {
352 // A key matching the one specified was already found...
353 throw new IllegalStateException("Unexpectedly found duplicate keys in the cache region matching the key specified.");
354 }
355 }
356 }
357 if (keyToRemove == null)
358 {
359 throw new IllegalStateException("No match for this key could be found in the set of keys retrieved from the memory cache.");
360 }
361 // At this point, we have retrieved the matching K key.
362
363 // Call remoteCacheServer.remove(String, Serializable)...
364 RemoteCacheServer<Serializable, Serializable> remoteCacheServer = RemoteCacheServerFactory.getRemoteCacheServer();
365 remoteCacheServer.remove(cacheName, key);
366 }
367 catch (Exception e)
368 {
369 throw new IllegalStateException("Failed to remove element with key [" + key + ", " + key.getClass() + "] from cache region [" + cacheName + "]: " + e, e);
370 }
371 }
372 }
373 }