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}