View Javadoc

1   package org.apache.jcs.engine.memory.lru;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.io.Serializable;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.Iterator;
27  import java.util.LinkedHashSet;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.jcs.engine.CacheConstants;
34  import org.apache.jcs.engine.behavior.ICacheElement;
35  import org.apache.jcs.engine.control.CompositeCache;
36  import org.apache.jcs.engine.control.group.GroupAttrName;
37  import org.apache.jcs.engine.memory.AbstractMemoryCache;
38  import org.apache.jcs.engine.memory.util.MemoryElementDescriptor;
39  import org.apache.jcs.engine.stats.StatElement;
40  import org.apache.jcs.engine.stats.Stats;
41  import org.apache.jcs.engine.stats.behavior.IStatElement;
42  import org.apache.jcs.engine.stats.behavior.IStats;
43  
44  /**
45   * This is a test memory manager using the jdk1.4 LinkedHashMap.
46   */
47  public class LHMLRUMemoryCache<K extends Serializable, V extends Serializable>
48      extends AbstractMemoryCache<K, V>
49  {
50      /** Don't change */
51      private static final long serialVersionUID = 6403738094136424101L;
52  
53      /** The Logger. */
54      protected final static Log log = LogFactory.getLog( LRUMemoryCache.class );
55  
56      /** number of hits */
57      protected int hitCnt = 0;
58  
59      /** number of misses */
60      protected int missCnt = 0;
61  
62      /** number of puts */
63      protected int putCnt = 0;
64  
65      /**
66       * For post reflection creation initialization
67       * <p>
68       * @param hub
69       */
70      @Override
71      public synchronized void initialize( CompositeCache<K, V> hub )
72      {
73          super.initialize( hub );
74          log.info( "initialized LHMLRUMemoryCache for " + cacheName );
75      }
76  
77      /**
78       * Returns a synchronized LHMSpooler
79       * <p>
80       * @return Collections.synchronizedMap( new LHMSpooler() )
81       */
82      @Override
83      public Map<K, MemoryElementDescriptor<K, V>> createMap()
84      {
85          return Collections.synchronizedMap( new LHMSpooler() );
86      }
87  
88      /**
89       * Puts an item to the cache.
90       * <p>
91       * @param ce Description of the Parameter
92       * @exception IOException
93       */
94      @Override
95      public void update( ICacheElement<K, V> ce )
96          throws IOException
97      {
98          putCnt++;
99          ce.getElementAttributes().setLastAccessTimeNow();
100         map.put( ce.getKey(), new MemoryElementDescriptor<K, V>(ce) );
101     }
102 
103     /**
104      * Get an item from the cache without affecting its last access time or position. There is no
105      * way to do this with the LinkedHashMap!
106      * <p>
107      * @param key Identifies item to find
108      * @return Element matching key if found, or null
109      * @exception IOException
110      */
111     @Override
112     public ICacheElement<K, V> getQuiet( K key )
113         throws IOException
114     {
115         return map.get( key ).ce;
116     }
117 
118     /**
119      * Get an item from the cache
120      * <p>
121      * @param key Identifies item to find
122      * @return ICacheElement<K, V> if found, else null
123      * @exception IOException
124      */
125     @Override
126     public synchronized ICacheElement<K, V> get( K key )
127         throws IOException
128     {
129         MemoryElementDescriptor<K, V> me = null;
130 
131         if ( log.isDebugEnabled() )
132         {
133             log.debug( "getting item from cache " + cacheName + " for key " + key );
134         }
135 
136         me = map.get( key );
137 
138         if ( me != null )
139         {
140             hitCnt++;
141             me.ce.getElementAttributes().setLastAccessTimeNow();
142             if ( log.isDebugEnabled() )
143             {
144                 log.debug( cacheName + ": LRUMemoryCache hit for " + key );
145             }
146             return me.ce;
147         }
148         else
149         {
150             missCnt++;
151             log.debug( cacheName + ": LRUMemoryCache miss for " + key );
152         }
153 
154         return null;
155     }
156 
157     /**
158      * Removes an item from the cache. This method handles hierarchical removal. If the key is a
159      * String and ends with the CacheConstants.NAME_COMPONENT_DELIMITER, then all items with keys
160      * starting with the argument String will be removed.
161      * <p>
162      * @param key
163      * @return true if removed
164      * @exception IOException
165      */
166     @Override
167     public synchronized boolean remove( K key )
168         throws IOException
169     {
170         if ( log.isDebugEnabled() )
171         {
172             log.debug( "removing item for key: " + key );
173         }
174 
175         boolean removed = false;
176 
177         // handle partial removal
178         if ( key instanceof String && ( (String) key ).endsWith( CacheConstants.NAME_COMPONENT_DELIMITER ) )
179         {
180             // remove all keys of the same name hierarchy.
181             synchronized ( map )
182             {
183                 for (Iterator<Map.Entry<K, MemoryElementDescriptor<K, V>>> itr = map.entrySet().iterator(); itr.hasNext(); )
184                 {
185                     Map.Entry<K, MemoryElementDescriptor<K, V>> entry = itr.next();
186                     K k = entry.getKey();
187 
188                     if ( k instanceof String && ( (String) k ).startsWith( key.toString() ) )
189                     {
190                         itr.remove();
191                         removed = true;
192                     }
193                 }
194             }
195         }
196         else if ( key instanceof GroupAttrName && ((GroupAttrName<?>)key).attrName == null )
197         {
198             // remove all keys of the same name hierarchy.
199             synchronized ( map )
200             {
201                 for (Iterator<Map.Entry<K, MemoryElementDescriptor<K, V>>> itr = map.entrySet().iterator(); itr.hasNext(); )
202                 {
203                     Map.Entry<K, MemoryElementDescriptor<K, V>> entry = itr.next();
204                     K k = entry.getKey();
205 
206                     if ( k instanceof GroupAttrName &&
207                         ((GroupAttrName<?>)k).groupId.equals(((GroupAttrName<?>)key).groupId) )
208                     {
209                         itr.remove();
210                         removed = true;
211                     }
212                 }
213             }
214         }
215         else
216         {
217             // remove single item.
218             MemoryElementDescriptor<K, V> me = map.remove( key );
219             if ( me != null )
220             {
221                 removed = true;
222             }
223         }
224 
225         return removed;
226     }
227 
228     /**
229      * Get an Array of the keys for all elements in the memory cache
230      * <p>
231      * @return An Object[]
232      */
233     @Override
234     public Set<K> getKeySet()
235     {
236         // need a better locking strategy here.
237         synchronized ( this )
238         {
239             // may need to lock to map here?
240             return new LinkedHashSet<K>(map.keySet());
241         }
242     }
243 
244     /**
245      * This returns semi-structured information on the memory cache, such as the size, put count,
246      * hit count, and miss count.
247      * <p>
248      * @return IStats
249      */
250     @Override
251     public synchronized IStats getStatistics()
252     {
253         IStats stats = new Stats();
254         stats.setTypeName( "LHMLRU Memory Cache" );
255 
256         ArrayList<IStatElement> elems = new ArrayList<IStatElement>();
257 
258         IStatElement se = null;
259 
260         se = new StatElement();
261         se.setName( "Map Size" );
262         se.setData( "" + map.size() );
263         elems.add( se );
264 
265         se = new StatElement();
266         se.setName( "Put Count" );
267         se.setData( "" + putCnt );
268         elems.add( se );
269 
270         se = new StatElement();
271         se.setName( "Hit Count" );
272         se.setData( "" + hitCnt );
273         elems.add( se );
274 
275         se = new StatElement();
276         se.setName( "Miss Count" );
277         se.setData( "" + missCnt );
278         elems.add( se );
279 
280         // get an array and put them in the Stats object
281         IStatElement[] ses = elems.toArray( new StatElement[0] );
282         stats.setStatElements( ses );
283 
284         // int rate = ((hitCnt + missCnt) * 100) / (hitCnt * 100) * 100;
285         // buf.append("\n Hit Rate = " + rate + " %" );
286 
287         return stats;
288     }
289 
290     // ---------------------------------------------------------- debug methods
291 
292     /**
293      * Dump the cache entries from first to last for debugging.
294      */
295     public void dumpCacheEntries()
296     {
297         dumpMap();
298     }
299 
300     /**
301      * This can't be implemented.
302      * <p>
303      * @param numberToFree
304      * @return 0
305      * @throws IOException
306      */
307     public int freeElements( int numberToFree )
308         throws IOException
309     {
310         // can't be implemented using the LHM
311         return 0;
312     }
313 
314     // ---------------------------------------------------------- extended map
315 
316     /**
317      * Implementation of removeEldestEntry in LinkedHashMap
318      */
319     public class LHMSpooler
320         extends java.util.LinkedHashMap<K, MemoryElementDescriptor<K, V>>
321     {
322         /** Don't change. */
323         private static final long serialVersionUID = -1255907868906762484L;
324 
325         /**
326          * Initialize to a small size--for now, 1/2 of max 3rd variable "true" indicates that it
327          * should be access and not time governed. This could be configurable.
328          */
329         public LHMSpooler()
330         {
331             super( (int) ( cache.getCacheAttributes().getMaxObjects() * .5 ), .75F, true );
332         }
333 
334         /**
335          * Remove eldest. Automatically called by LinkedHashMap.
336          * <p>
337          * @param eldest
338          * @return true if removed
339          */
340         @Override
341         protected boolean removeEldestEntry( Map.Entry<K, MemoryElementDescriptor<K, V>> eldest )
342         {
343             ICacheElement<K, V> element = eldest.getValue().ce;
344 
345             if ( size() <= cache.getCacheAttributes().getMaxObjects() )
346             {
347                 return false;
348             }
349             else
350             {
351 
352                 if ( log.isDebugEnabled() )
353                 {
354                     log.debug( "LHMLRU max size: " + cache.getCacheAttributes().getMaxObjects()
355                         + ".  Spooling element, key: " + element.getKey() );
356                 }
357                 spoolToDisk( element );
358 
359                 if ( log.isDebugEnabled() )
360                 {
361                     log.debug( "LHMLRU size: " + map.size() );
362                 }
363             }
364             return true;
365         }
366 
367         /**
368          * Puts the element in the DiskStore
369          * <p>
370          * @param element The CacheElement
371          */
372         private void spoolToDisk( ICacheElement<K, V> element )
373         {
374             cache.spoolToDisk( element );
375 
376             if ( log.isDebugEnabled() )
377             {
378                 log.debug( cache.getCacheName() + "Spooled element to disk: " + element.getKey() );
379             }
380         }
381     }
382 }