View Javadoc
1   package org.apache.commons.jcs3.engine.memory;
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.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.LinkedHashSet;
26  import java.util.Map;
27  import java.util.Objects;
28  import java.util.Set;
29  import java.util.concurrent.atomic.AtomicLong;
30  import java.util.concurrent.locks.Lock;
31  import java.util.concurrent.locks.ReentrantLock;
32  import java.util.stream.Collectors;
33  
34  import org.apache.commons.jcs3.engine.behavior.ICache;
35  import org.apache.commons.jcs3.engine.behavior.ICacheElement;
36  import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
37  import org.apache.commons.jcs3.engine.control.CompositeCache;
38  import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
39  import org.apache.commons.jcs3.engine.control.group.GroupId;
40  import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache;
41  import org.apache.commons.jcs3.engine.memory.util.MemoryElementDescriptor;
42  import org.apache.commons.jcs3.engine.stats.StatElement;
43  import org.apache.commons.jcs3.engine.stats.Stats;
44  import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
45  import org.apache.commons.jcs3.engine.stats.behavior.IStats;
46  import org.apache.commons.jcs3.log.Log;
47  import org.apache.commons.jcs3.log.LogManager;
48  
49  /**
50   * This base includes some common code for memory caches.
51   */
52  public abstract class AbstractMemoryCache<K, V>
53      implements IMemoryCache<K, V>
54  {
55      /** Log instance */
56      private static final Log log = LogManager.getLog( AbstractMemoryCache.class );
57  
58      /** Cache Attributes.  Regions settings. */
59      private ICompositeCacheAttributes cacheAttributes;
60  
61      /** The cache region this store is associated with */
62      private CompositeCache<K, V> cache;
63  
64      /** How many to spool at a time. */
65      protected int chunkSize;
66  
67      protected final Lock lock = new ReentrantLock();
68  
69      /** Map where items are stored by key.  This is created by the concrete child class. */
70      protected Map<K, MemoryElementDescriptor<K, V>> map;// TODO privatise
71  
72      /** number of hits */
73      protected AtomicLong hitCnt;
74  
75      /** number of misses */
76      protected AtomicLong missCnt;
77  
78      /** number of puts */
79      protected AtomicLong putCnt;
80  
81      /**
82       * For post reflection creation initialization
83       * <p>
84       * @param hub
85       */
86      @Override
87      public void initialize( final CompositeCache<K, V> hub )
88      {
89          hitCnt = new AtomicLong();
90          missCnt = new AtomicLong();
91          putCnt = new AtomicLong();
92  
93          this.cacheAttributes = hub.getCacheAttributes();
94          this.chunkSize = cacheAttributes.getSpoolChunkSize();
95          this.cache = hub;
96  
97          this.map = createMap();
98      }
99  
100     /**
101      * Children must implement this method. A FIFO implementation may use a tree map. An LRU might
102      * use a hashtable. The map returned should be threadsafe.
103      * <p>
104      * @return a threadsafe Map
105      */
106     public abstract Map<K, MemoryElementDescriptor<K, V>> createMap();
107 
108     /**
109      * Gets multiple items from the cache based on the given set of keys.
110      * <p>
111      * @param keys
112      * @return a map of K key to ICacheElement&lt;K, V&gt; element, or an empty map if there is no
113      *         data in cache for any of these keys
114      * @throws IOException
115      */
116     @Override
117     public Map<K, ICacheElement<K, V>> getMultiple(final Set<K> keys)
118         throws IOException
119     {
120         if (keys != null)
121         {
122             return keys.stream()
123                 .map(key -> {
124                     try
125                     {
126                         return get(key);
127                     }
128                     catch (final IOException e)
129                     {
130                         return null;
131                     }
132                 })
133                 .filter(Objects::nonNull)
134                 .collect(Collectors.toMap(
135                         ICacheElement::getKey,
136                         element -> element));
137         }
138 
139         return new HashMap<>();
140     }
141 
142     /**
143      * Get an item from the cache without affecting its last access time or position. Not all memory
144      * cache implementations can get quietly.
145      * <p>
146      * @param key Identifies item to find
147      * @return Element matching key if found, or null
148      * @throws IOException
149      */
150     @Override
151     public ICacheElement<K, V> getQuiet( final K key )
152         throws IOException
153     {
154         ICacheElement<K, V> ce = null;
155 
156         final MemoryElementDescriptor<K, V> me = map.get( key );
157         if ( me != null )
158         {
159             log.debug( "{0}: MemoryCache quiet hit for {1}",
160                     this::getCacheName, () -> key );
161 
162             ce = me.getCacheElement();
163         }
164         else
165         {
166             log.debug( "{0}: MemoryCache quiet miss for {1}",
167                     this::getCacheName, () -> key );
168         }
169 
170         return ce;
171     }
172 
173     /**
174      * Puts an item to the cache.
175      * <p>
176      * @param ce Description of the Parameter
177      * @throws IOException Description of the Exception
178      */
179     @Override
180     public abstract void update( ICacheElement<K, V> ce )
181         throws IOException;
182 
183     /**
184      * Removes all cached items from the cache.
185      * <p>
186      * @throws IOException
187      */
188     @Override
189     public void removeAll() throws IOException
190     {
191         lock.lock();
192         try
193         {
194             lockedRemoveAll();
195             map.clear();
196         }
197         finally
198         {
199             lock.unlock();
200         }
201     }
202 
203     /**
204      * Removes all cached items from the cache control structures.
205      * (guarded by the lock)
206      */
207     protected abstract void lockedRemoveAll();
208 
209     /**
210      * Prepares for shutdown. Reset statistics
211      * <p>
212      * @throws IOException
213      */
214     @Override
215     public void dispose()
216         throws IOException
217     {
218         removeAll();
219         hitCnt.set(0);
220         missCnt.set(0);
221         putCnt.set(0);
222         log.info( "Memory Cache dispose called." );
223     }
224 
225     /**
226      * @return statistics about the cache
227      */
228     @Override
229     public IStats getStatistics()
230     {
231         final IStats stats = new Stats();
232         stats.setTypeName( "Abstract Memory Cache" );
233 
234         final ArrayList<IStatElement<?>> elems = new ArrayList<>();
235         stats.setStatElements(elems);
236 
237         elems.add(new StatElement<>("Put Count", putCnt));
238         elems.add(new StatElement<>("Hit Count", hitCnt));
239         elems.add(new StatElement<>("Miss Count", missCnt));
240         elems.add(new StatElement<>( "Map Size", Integer.valueOf(getSize()) ) );
241 
242         return stats;
243     }
244 
245     /**
246      * Returns the current cache size.
247      * <p>
248      * @return The size value
249      */
250     @Override
251     public int getSize()
252     {
253         return this.map.size();
254     }
255 
256     /**
257      * Returns the cache (aka "region") name.
258      * <p>
259      * @return The cacheName value
260      */
261     public String getCacheName()
262     {
263         final String attributeCacheName = this.cacheAttributes.getCacheName();
264         if(attributeCacheName != null)
265         {
266             return attributeCacheName;
267         }
268         return cache.getCacheName();
269     }
270 
271     /**
272      * Puts an item to the cache.
273      * <p>
274      * @param ce the item
275      */
276     @Override
277     public void waterfal( final ICacheElement<K, V> ce )
278     {
279         this.cache.spoolToDisk( ce );
280     }
281 
282     // ---------------------------------------------------------- debug method
283     /**
284      * Dump the cache map for debugging.
285      */
286     public void dumpMap()
287     {
288         if (log.isTraceEnabled())
289         {
290             log.trace("dumpingMap");
291             map.forEach((key, value) ->
292                 log.trace("dumpMap> key={0}, val={1}",key, key,
293                         value.getCacheElement().getVal()));
294         }
295     }
296 
297     /**
298      * Returns the CacheAttributes.
299      * <p>
300      * @return The CacheAttributes value
301      */
302     @Override
303     public ICompositeCacheAttributes getCacheAttributes()
304     {
305         return this.cacheAttributes;
306     }
307 
308     /**
309      * Sets the CacheAttributes.
310      * <p>
311      * @param cattr The new CacheAttributes value
312      */
313     @Override
314     public void setCacheAttributes( final ICompositeCacheAttributes cattr )
315     {
316         this.cacheAttributes = cattr;
317     }
318 
319     /**
320      * Gets the cache hub / region that the MemoryCache is used by
321      * <p>
322      * @return The cache value
323      */
324     @Override
325     public CompositeCache<K, V> getCompositeCache()
326     {
327         return this.cache;
328     }
329 
330     /**
331      * Remove all keys of the same group hierarchy.
332      * @param key the key
333      * @return true if something has been removed
334      */
335     protected boolean removeByGroup(final K key)
336     {
337         final GroupId groupId = ((GroupAttrName<?>) key).groupId;
338 
339         // remove all keys of the same group hierarchy.
340         return map.entrySet().removeIf(entry -> {
341             final K k = entry.getKey();
342 
343             if (k instanceof GroupAttrName && ((GroupAttrName<?>) k).groupId.equals(groupId))
344             {
345                 lock.lock();
346                 try
347                 {
348                     lockedRemoveElement(entry.getValue());
349                     return true;
350                 }
351                 finally
352                 {
353                     lock.unlock();
354                 }
355             }
356 
357             return false;
358         });
359     }
360 
361     /**
362      * Remove all keys of the same name hierarchy.
363      *
364      * @param key the key
365      * @return true if something has been removed
366      */
367     protected boolean removeByHierarchy(final K key)
368     {
369         final String keyString = key.toString();
370 
371         // remove all keys of the same name hierarchy.
372         return map.entrySet().removeIf(entry -> {
373             final K k = entry.getKey();
374 
375             if (k instanceof String && ((String) k).startsWith(keyString))
376             {
377                 lock.lock();
378                 try
379                 {
380                     lockedRemoveElement(entry.getValue());
381                     return true;
382                 }
383                 finally
384                 {
385                     lock.unlock();
386                 }
387             }
388 
389             return false;
390         });
391     }
392 
393     /**
394      * Remove element from control structure
395      * (guarded by the lock)
396      *
397      * @param me the memory element descriptor
398      */
399     protected abstract void lockedRemoveElement(MemoryElementDescriptor<K, V> me);
400 
401     /**
402      * Removes an item from the cache. This method handles hierarchical removal. If the key is a
403      * String and ends with the CacheConstants.NAME_COMPONENT_DELIMITER, then all items with keys
404      * starting with the argument String will be removed.
405      * <p>
406      *
407      * @param key
408      * @return true if the removal was successful
409      * @throws IOException
410      */
411     @Override
412     public boolean remove(final K key) throws IOException
413     {
414         log.debug("removing item for key: {0}", key);
415 
416         boolean removed = false;
417 
418         // handle partial removal
419         if (key instanceof String && ((String) key).endsWith(ICache.NAME_COMPONENT_DELIMITER))
420         {
421             removed = removeByHierarchy(key);
422         }
423         else if (key instanceof GroupAttrName && ((GroupAttrName<?>) key).attrName == null)
424         {
425             removed = removeByGroup(key);
426         }
427         else
428         {
429             // remove single item.
430             lock.lock();
431             try
432             {
433                 final MemoryElementDescriptor<K, V> me = map.remove(key);
434                 if (me != null)
435                 {
436                     lockedRemoveElement(me);
437                     removed = true;
438                 }
439             }
440             finally
441             {
442                 lock.unlock();
443             }
444         }
445 
446         return removed;
447     }
448 
449     /**
450      * Get an Array of the keys for all elements in the memory cache
451      *
452      * @return An Object[]
453      */
454     @Override
455     public Set<K> getKeySet()
456     {
457         return new LinkedHashSet<>(map.keySet());
458     }
459 
460     /**
461      * Get an item from the cache.
462      * <p>
463      *
464      * @param key Identifies item to find
465      * @return ICacheElement&lt;K, V&gt; if found, else null
466      * @throws IOException
467      */
468     @Override
469     public ICacheElement<K, V> get(final K key) throws IOException
470     {
471         ICacheElement<K, V> ce = null;
472 
473         log.debug("{0}: getting item for key {1}", this::getCacheName,
474                 () -> key);
475 
476         final MemoryElementDescriptor<K, V> me = map.get(key);
477 
478         if (me != null)
479         {
480             hitCnt.incrementAndGet();
481             ce = me.getCacheElement();
482 
483             lock.lock();
484             try
485             {
486                 lockedGetElement(me);
487             }
488             finally
489             {
490                 lock.unlock();
491             }
492 
493             log.debug("{0}: MemoryCache hit for {1}", this::getCacheName,
494                     () -> key);
495         }
496         else
497         {
498             missCnt.incrementAndGet();
499 
500             log.debug("{0}: MemoryCache miss for {1}", this::getCacheName,
501                     () -> key);
502         }
503 
504         return ce;
505     }
506 
507     /**
508      * Update control structures after get
509      * (guarded by the lock)
510      *
511      * @param me the memory element descriptor
512      */
513     protected abstract void lockedGetElement(MemoryElementDescriptor<K, V> me);
514 }