View Javadoc
1   package org.apache.commons.jcs.engine.memory.soft;
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.lang.ref.SoftReference;
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.concurrent.ConcurrentHashMap;
30  import java.util.concurrent.ConcurrentMap;
31  import java.util.concurrent.LinkedBlockingQueue;
32  
33  import org.apache.commons.jcs.engine.CacheConstants;
34  import org.apache.commons.jcs.engine.behavior.ICacheElement;
35  import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
36  import org.apache.commons.jcs.engine.control.CompositeCache;
37  import org.apache.commons.jcs.engine.control.group.GroupAttrName;
38  import org.apache.commons.jcs.engine.memory.AbstractMemoryCache;
39  import org.apache.commons.jcs.engine.memory.util.MemoryElementDescriptor;
40  import org.apache.commons.jcs.engine.memory.util.SoftReferenceElementDescriptor;
41  import org.apache.commons.jcs.engine.stats.StatElement;
42  import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
43  import org.apache.commons.jcs.engine.stats.behavior.IStats;
44  import org.apache.commons.logging.Log;
45  import org.apache.commons.logging.LogFactory;
46  
47  /**
48   * A JCS IMemoryCache that has {@link SoftReference} to all its values.
49   * This cache does not respect {@link ICompositeCacheAttributes#getMaxObjects()}
50   * as overflowing is handled by Java GC.
51   * <p>
52   * The cache also has strong references to a maximum number of objects given by
53   * the maxObjects parameter
54   *
55   * @author halset
56   */
57  public class SoftReferenceMemoryCache<K, V> extends AbstractMemoryCache<K, V>
58  {
59      /** The logger. */
60      private static final Log log = LogFactory.getLog(SoftReferenceMemoryCache.class);
61  
62      /**
63       * Strong references to the maxObjects number of newest objects.
64       * <p>
65       * Trimming is done by {@link #trimStrongReferences()} instead of by
66       * overriding removeEldestEntry to be able to control waterfalling as easy
67       * as possible
68       */
69      private LinkedBlockingQueue<ICacheElement<K, V>> strongReferences;
70  
71      /**
72       * For post reflection creation initialization
73       * <p>
74       * @param hub
75       */
76      @Override
77      public synchronized void initialize( CompositeCache<K, V> hub )
78      {
79          super.initialize( hub );
80          strongReferences = new LinkedBlockingQueue<ICacheElement<K, V>>();
81          log.info( "initialized Soft Reference Memory Cache for " + getCacheName() );
82      }
83  
84      /**
85       * @see org.apache.commons.jcs.engine.memory.AbstractMemoryCache#createMap()
86       */
87      @Override
88      public ConcurrentMap<K, MemoryElementDescriptor<K, V>> createMap()
89      {
90          return new ConcurrentHashMap<K, MemoryElementDescriptor<K, V>>();
91      }
92  
93      /**
94       * @see org.apache.commons.jcs.engine.memory.behavior.IMemoryCache#getKeySet()
95       */
96      @Override
97      public Set<K> getKeySet()
98      {
99          Set<K> keys = new HashSet<K>();
100         for (Map.Entry<K, MemoryElementDescriptor<K, V>> e : map.entrySet())
101         {
102             SoftReferenceElementDescriptor<K, V> sred = (SoftReferenceElementDescriptor<K, V>) e.getValue();
103             if (sred.getCacheElement() != null)
104             {
105                 keys.add(e.getKey());
106             }
107         }
108 
109         return keys;
110     }
111 
112     /**
113      * Returns the current cache size.
114      * <p>
115      * @return The size value
116      */
117     @Override
118     public int getSize()
119     {
120         int size = 0;
121         for (MemoryElementDescriptor<K, V> me : map.values())
122         {
123             SoftReferenceElementDescriptor<K, V> sred = (SoftReferenceElementDescriptor<K, V>) me;
124             if (sred.getCacheElement() != null)
125             {
126                 size++;
127             }
128         }
129         return size;
130     }
131 
132     /**
133      * @return statistics about the cache
134      */
135     @Override
136     public IStats getStatistics()
137     {
138         IStats stats = super.getStatistics();
139         stats.setTypeName("Soft Reference Memory Cache");
140 
141         List<IStatElement<?>> elems = stats.getStatElements();
142         int emptyrefs = map.size() - getSize();
143         elems.add(new StatElement<Integer>("Empty References", Integer.valueOf(emptyrefs)));
144         elems.add(new StatElement<Integer>("Strong References", Integer.valueOf(strongReferences.size())));
145 
146         return stats;
147     }
148 
149     /**
150      * Removes an item from the cache. This method handles hierarchical removal. If the key is a
151      * String and ends with the CacheConstants.NAME_COMPONENT_DELIMITER, then all items with keys
152      * starting with the argument String will be removed.
153      * <p>
154      *
155      * @param key
156      * @return true if the removal was successful
157      * @throws IOException
158      */
159     @Override
160     public boolean remove(K key) throws IOException
161     {
162         if (log.isDebugEnabled())
163         {
164             log.debug("removing item for key: " + key);
165         }
166 
167         boolean removed = false;
168 
169         // handle partial removal
170         if (key instanceof String && ((String) key).endsWith(CacheConstants.NAME_COMPONENT_DELIMITER))
171         {
172             // remove all keys of the same name hierarchy.
173             for (Iterator<Map.Entry<K, MemoryElementDescriptor<K, V>>> itr = map.entrySet().iterator();
174                     itr.hasNext();)
175             {
176                 Map.Entry<K, MemoryElementDescriptor<K, V>> entry = itr.next();
177                 K k = entry.getKey();
178 
179                 if (k instanceof String && ((String) k).startsWith(key.toString()))
180                 {
181                     lock.lock();
182                     try
183                     {
184                         strongReferences.remove(entry.getValue().getCacheElement());
185                         itr.remove();
186                         removed = true;
187                     }
188                     finally
189                     {
190                         lock.unlock();
191                     }
192                 }
193             }
194         }
195         else if (key instanceof GroupAttrName && ((GroupAttrName<?>) key).attrName == null)
196         {
197             // remove all keys of the same name hierarchy.
198             for (Iterator<Map.Entry<K, MemoryElementDescriptor<K, V>>> itr = map.entrySet().iterator();
199                     itr.hasNext();)
200             {
201                 Map.Entry<K, MemoryElementDescriptor<K, V>> entry = itr.next();
202                 K k = entry.getKey();
203 
204                 if (k instanceof GroupAttrName && ((GroupAttrName<?>) k).groupId.equals(((GroupAttrName<?>) key).groupId))
205                 {
206                     lock.lock();
207                     try
208                     {
209                         strongReferences.remove(entry.getValue().getCacheElement());
210                         itr.remove();
211                         removed = true;
212                     }
213                     finally
214                     {
215                         lock.unlock();
216                     }
217                 }
218             }
219         }
220         else
221         {
222             // remove single item.
223             lock.lock();
224             try
225             {
226                 MemoryElementDescriptor<K, V> me = map.remove(key);
227                 if (me != null)
228                 {
229                     strongReferences.remove(me.getCacheElement());
230                     removed = true;
231                 }
232             }
233             finally
234             {
235                 lock.unlock();
236             }
237         }
238 
239         return removed;
240     }
241 
242     /**
243      * Removes all cached items from the cache.
244      * <p>
245      * @throws IOException
246      */
247     @Override
248     public void removeAll() throws IOException
249     {
250         super.removeAll();
251         strongReferences.clear();
252     }
253 
254     /**
255      * Puts an item to the cache.
256      * <p>
257      * @param ce Description of the Parameter
258      * @throws IOException Description of the Exception
259      */
260     @Override
261     public void update(ICacheElement<K, V> ce) throws IOException
262     {
263         putCnt.incrementAndGet();
264         ce.getElementAttributes().setLastAccessTimeNow();
265 
266         lock.lock();
267 
268         try
269         {
270             map.put(ce.getKey(), new SoftReferenceElementDescriptor<K, V>(ce));
271             strongReferences.add(ce);
272             trimStrongReferences();
273         }
274         finally
275         {
276             lock.unlock();
277         }
278     }
279 
280     /**
281      * Trim the number of strong references to equal or below the number given
282      * by the maxObjects parameter.
283      */
284     private void trimStrongReferences()
285     {
286         int max = getCacheAttributes().getMaxObjects();
287         int startsize = strongReferences.size();
288 
289         for (int cursize = startsize; cursize > max; cursize--)
290         {
291             ICacheElement<K, V> ce = strongReferences.poll();
292             waterfal(ce);
293         }
294     }
295 
296     /**
297      * Get an item from the cache
298      * <p>
299      * @param key Description of the Parameter
300      * @return Description of the Return Value
301      * @throws IOException Description of the Exception
302      */
303     @Override
304     public ICacheElement<K, V> get(K key) throws IOException
305     {
306         ICacheElement<K, V> val = null;
307         lock.lock();
308 
309         try
310         {
311             val = getQuiet(key);
312             if (val != null)
313             {
314                 val.getElementAttributes().setLastAccessTimeNow();
315 
316                 // update the ordering of the strong references
317                 strongReferences.add(val);
318                 trimStrongReferences();
319             }
320         }
321         finally
322         {
323             lock.unlock();
324         }
325 
326         if (val == null)
327         {
328             missCnt.incrementAndGet();
329         }
330         else
331         {
332             hitCnt.incrementAndGet();
333         }
334 
335         return val;
336     }
337 
338     /**
339      * This can't be implemented.
340      * <p>
341      * @param numberToFree
342      * @return 0
343      * @throws IOException
344      */
345     @Override
346     public int freeElements(int numberToFree) throws IOException
347     {
348         return 0;
349     }
350 }