|
| 1 | +package org.apache.jcs.servlet; |
| 2 | + |
| 3 | +import java.io.IOException; |
| 4 | +import java.io.OutputStream; |
| 5 | +import java.io.ObjectOutputStream; |
| 6 | +import java.util.Arrays; |
| 7 | +import java.util.LinkedList; |
| 8 | +import java.util.Iterator; |
| 9 | +import java.util.Map; |
| 10 | +import javax.servlet.http.HttpServletRequest; |
| 11 | +import javax.servlet.http.HttpServletResponse; |
| 12 | + |
| 13 | +import org.apache.jcs.engine.CacheConstants; |
| 14 | +import org.apache.jcs.engine.memory.MemoryCache; |
| 15 | +import org.apache.jcs.engine.memory.MemoryElementDescriptor; |
| 16 | +import org.apache.jcs.engine.behavior.ICache; |
| 17 | +import org.apache.jcs.engine.control.CacheHub; |
| 18 | +import org.apache.jcs.engine.control.Cache; |
| 19 | +import org.apache.velocity.Template; |
| 20 | +import org.apache.velocity.context.Context; |
| 21 | +import org.apache.velocity.servlet.VelocityServlet; |
| 22 | + |
| 23 | +/** |
| 24 | + * A servlet which provides HTTP access to JCS. Allows a summary of regions |
| 25 | + * to be viewed, and removeAll to be run on individual regions or all regions. |
| 26 | + * Also provides the ability to remove items (any number of key arguments can |
| 27 | + * be provided with action 'remove'). Should be initialized with a properties |
| 28 | + * file that provides at least a classpath resource loader. Since this extends |
| 29 | + * VelocityServlet, which uses the singleton model for velocity, it will share |
| 30 | + * configuration with any other Velocity in the same JVM. |
| 31 | + * |
| 32 | + * Initialization in a webapp will look something like this: |
| 33 | + * <pre> |
| 34 | + * [servlet] |
| 35 | + * [servlet-name]JCSAdminServlet[/servlet-name] |
| 36 | + * [servlet-class]org.apache.jcs.servlet.JCSAdminServlet[/servlet-class] |
| 37 | + * [init-param] |
| 38 | + * [param-name]properties[/param-name] |
| 39 | + * [param-value]WEB-INF/conf/JCSAdminServlet.velocity.properties[/param-value] |
| 40 | + * [/init-param] |
| 41 | + * [/servlet] |
| 42 | + * </pre> |
| 43 | + * |
| 44 | + * FIXME: It would be nice to use the VelocityEngine model so this can be truly |
| 45 | + * standalone. Right now if you run it in the same container as, say, |
| 46 | + * turbine, turbine must be run first to ensure it's config takes |
| 47 | + * precedence. |
| 48 | + * |
| 49 | + * @author <a href="mailto:james@jamestaylor.org">James Taylor</a> |
| 50 | + * @version $Id$ |
| 51 | + */ |
| 52 | +public class JCSAdminServlet extends VelocityServlet |
| 53 | +{ |
| 54 | + private static final String DEFAULT_TEMPLATE_NAME = |
| 55 | + "/org/apache/jcs/servlet/JCSAdminServletDefault.vm"; |
| 56 | + |
| 57 | + // Keys for parameters |
| 58 | + |
| 59 | + private static final String CACHE_NAME_PARAM = "cacheName"; |
| 60 | + |
| 61 | + private static final String ACTION_PARAM = "action"; |
| 62 | + private static final String KEY_PARAM = "key"; |
| 63 | + |
| 64 | + // Possible values for 'action' parameter |
| 65 | + |
| 66 | + private static final String CLEAR_ALL_REGIONS_ACTION = "clearAllRegions"; |
| 67 | + private static final String CLEAR_REGION_ACTION = "clearRegion"; |
| 68 | + private static final String REMOVE_ACTION = "remove"; |
| 69 | + |
| 70 | + private CacheHub cacheHub = CacheHub.getInstance(); |
| 71 | + |
| 72 | + /** @see VelocityServlet#handleRequest */ |
| 73 | + protected Template handleRequest( HttpServletRequest request, |
| 74 | + HttpServletResponse response, |
| 75 | + Context context ) |
| 76 | + throws Exception |
| 77 | + { |
| 78 | + String cacheName = request.getParameter( CACHE_NAME_PARAM ); |
| 79 | + |
| 80 | + // If an action was provided, handle it |
| 81 | + |
| 82 | + String action = request.getParameter( ACTION_PARAM ); |
| 83 | + |
| 84 | + if ( action != null ) |
| 85 | + { |
| 86 | + if ( action.equals( CLEAR_ALL_REGIONS_ACTION ) ) |
| 87 | + { |
| 88 | + clearAllRegions(); |
| 89 | + } |
| 90 | + else if ( action.equals( CLEAR_REGION_ACTION ) ) |
| 91 | + { |
| 92 | + if ( cacheName == null ) |
| 93 | + { |
| 94 | + // Not Allowed |
| 95 | + } |
| 96 | + else |
| 97 | + { |
| 98 | + clearRegion( cacheName ); |
| 99 | + } |
| 100 | + } |
| 101 | + else if ( action.equals( REMOVE_ACTION ) ) |
| 102 | + { |
| 103 | + String[] keys = request.getParameterValues( KEY_PARAM ); |
| 104 | + |
| 105 | + for ( int i = 0; i < keys.length; i++ ) |
| 106 | + { |
| 107 | + removeItem( cacheName, keys[ i ] ); |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + // Now populate the context |
| 113 | + |
| 114 | + String[] cacheNames = cacheHub.getCacheNames(); |
| 115 | + |
| 116 | + Arrays.sort( cacheNames ); |
| 117 | + |
| 118 | + LinkedList cacheInfo = new LinkedList(); |
| 119 | + |
| 120 | + CacheRegionInfo regionInfo; |
| 121 | + Cache cache; |
| 122 | + |
| 123 | + for ( int i = 0; i < cacheNames.length; i++ ) |
| 124 | + { |
| 125 | + cache = ( Cache ) cacheHub.getCache( cacheNames[ i ] ); |
| 126 | + |
| 127 | + regionInfo = new CacheRegionInfo(); |
| 128 | + |
| 129 | + regionInfo.name = cache.getCacheName(); |
| 130 | + regionInfo.itemCount = cache.getSize(); |
| 131 | + regionInfo.byteCount = getByteCount( cache ); |
| 132 | + regionInfo.status = statusAsString( cache ); |
| 133 | + |
| 134 | + cacheInfo.add( regionInfo ); |
| 135 | + } |
| 136 | + |
| 137 | + context.put( "cacheInfoRecords", cacheInfo ); |
| 138 | + |
| 139 | + return getTemplate( DEFAULT_TEMPLATE_NAME ); |
| 140 | + } |
| 141 | + |
| 142 | + public int getByteCount( Cache cache ) |
| 143 | + throws Exception |
| 144 | + { |
| 145 | + MemoryCache memCache = cache.getMemoryCache(); |
| 146 | + |
| 147 | + Iterator iter = memCache.getIterator(); |
| 148 | + |
| 149 | + CountingOnlyOutputStream counter = new CountingOnlyOutputStream(); |
| 150 | + ObjectOutputStream out = new ObjectOutputStream( counter ); |
| 151 | + |
| 152 | + while ( iter.hasNext() ) |
| 153 | + { |
| 154 | + MemoryElementDescriptor node = ( MemoryElementDescriptor ) |
| 155 | + ( ( Map.Entry ) iter.next() ).getValue(); |
| 156 | + |
| 157 | + out.writeObject( node.ce.getVal() ); |
| 158 | + } |
| 159 | + |
| 160 | + // 4 bytes lost for the serialization header |
| 161 | + |
| 162 | + return counter.getCount() - 4; |
| 163 | + } |
| 164 | + |
| 165 | + private String statusAsString( ICache cache ) |
| 166 | + { |
| 167 | + int status = cache.getStatus(); |
| 168 | + |
| 169 | + return ( status == CacheConstants.STATUS_ALIVE ? "ALIVE" |
| 170 | + : status == CacheConstants.STATUS_DISPOSED ? "DISPOSED" |
| 171 | + : status == CacheConstants.STATUS_ERROR ? "ERROR" |
| 172 | + : "UNKNOWN" ); |
| 173 | + } |
| 174 | + |
| 175 | + private void clearAllRegions() throws IOException |
| 176 | + { |
| 177 | + String[] names = cacheHub.getCacheNames(); |
| 178 | + |
| 179 | + for ( int i = 0; i < names.length; i++ ) |
| 180 | + { |
| 181 | + cacheHub.getCache( names[ i ] ).removeAll(); |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + private void clearRegion( String cacheName ) throws IOException |
| 186 | + { |
| 187 | + cacheHub.getCache( cacheName ).removeAll(); |
| 188 | + } |
| 189 | + |
| 190 | + private void removeItem( String cacheName, String key ) throws IOException |
| 191 | + { |
| 192 | + cacheHub.getCache( cacheName ).remove( key ); |
| 193 | + } |
| 194 | + |
| 195 | + /** Stores info on a cache region for the template */ |
| 196 | + public class CacheRegionInfo |
| 197 | + { |
| 198 | + String name = null; |
| 199 | + long itemCount = 0; |
| 200 | + long byteCount = 0; |
| 201 | + String status = null; |
| 202 | + |
| 203 | + public String getName() |
| 204 | + { |
| 205 | + return name; |
| 206 | + } |
| 207 | + |
| 208 | + public long getItemCount() |
| 209 | + { |
| 210 | + return itemCount; |
| 211 | + } |
| 212 | + |
| 213 | + public long getByteCount() |
| 214 | + { |
| 215 | + return byteCount; |
| 216 | + } |
| 217 | + |
| 218 | + public String getStatus() |
| 219 | + { |
| 220 | + return status; |
| 221 | + } |
| 222 | + } |
| 223 | + |
| 224 | + /** |
| 225 | + * Keeps track of the number of bytes written to it, but doesn't write them |
| 226 | + * anywhere. |
| 227 | + */ |
| 228 | + private static class CountingOnlyOutputStream extends OutputStream |
| 229 | + { |
| 230 | + private int count; |
| 231 | + |
| 232 | + public void write( byte[] b ) throws IOException |
| 233 | + { |
| 234 | + count += b.length; |
| 235 | + } |
| 236 | + |
| 237 | + public void write( byte[] b, int off, int len ) throws IOException |
| 238 | + { |
| 239 | + count += len; |
| 240 | + } |
| 241 | + |
| 242 | + public void write( int b ) throws IOException |
| 243 | + { |
| 244 | + count++; |
| 245 | + } |
| 246 | + |
| 247 | + /** |
| 248 | + * The number of bytes that have passed through this stream. |
| 249 | + */ |
| 250 | + public int getCount() |
| 251 | + { |
| 252 | + return this.count; |
| 253 | + } |
| 254 | + } |
| 255 | +} |
0 commit comments