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    }