001package org.apache.commons.jcs3.engine.memory;
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
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.LinkedHashSet;
026import java.util.Map;
027import java.util.Objects;
028import java.util.Set;
029import java.util.concurrent.atomic.AtomicLong;
030import java.util.concurrent.locks.Lock;
031import java.util.concurrent.locks.ReentrantLock;
032import java.util.stream.Collectors;
033
034import org.apache.commons.jcs3.engine.behavior.ICache;
035import org.apache.commons.jcs3.engine.behavior.ICacheElement;
036import org.apache.commons.jcs3.engine.behavior.ICompositeCacheAttributes;
037import org.apache.commons.jcs3.engine.control.CompositeCache;
038import org.apache.commons.jcs3.engine.control.group.GroupAttrName;
039import org.apache.commons.jcs3.engine.control.group.GroupId;
040import org.apache.commons.jcs3.engine.memory.behavior.IMemoryCache;
041import org.apache.commons.jcs3.engine.memory.util.MemoryElementDescriptor;
042import org.apache.commons.jcs3.engine.stats.StatElement;
043import org.apache.commons.jcs3.engine.stats.Stats;
044import org.apache.commons.jcs3.engine.stats.behavior.IStatElement;
045import org.apache.commons.jcs3.engine.stats.behavior.IStats;
046import org.apache.commons.jcs3.log.Log;
047import org.apache.commons.jcs3.log.LogManager;
048
049/**
050 * This base includes some common code for memory caches.
051 */
052public abstract class AbstractMemoryCache<K, V>
053    implements IMemoryCache<K, V>
054{
055    /** Log instance */
056    private static final Log log = LogManager.getLog( AbstractMemoryCache.class );
057
058    /** Cache Attributes.  Regions settings. */
059    private ICompositeCacheAttributes cacheAttributes;
060
061    /** The cache region this store is associated with */
062    private CompositeCache<K, V> cache;
063
064    /** How many to spool at a time. */
065    protected int chunkSize;
066
067    protected final Lock lock = new ReentrantLock();
068
069    /** Map where items are stored by key.  This is created by the concrete child class. */
070    protected Map<K, MemoryElementDescriptor<K, V>> map;// TODO privatise
071
072    /** number of hits */
073    protected AtomicLong hitCnt;
074
075    /** number of misses */
076    protected AtomicLong missCnt;
077
078    /** number of puts */
079    protected AtomicLong putCnt;
080
081    /**
082     * For post reflection creation initialization
083     * <p>
084     * @param hub
085     */
086    @Override
087    public void initialize( final CompositeCache<K, V> hub )
088    {
089        hitCnt = new AtomicLong();
090        missCnt = new AtomicLong();
091        putCnt = new AtomicLong();
092
093        this.cacheAttributes = hub.getCacheAttributes();
094        this.chunkSize = cacheAttributes.getSpoolChunkSize();
095        this.cache = hub;
096
097        this.map = createMap();
098    }
099
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}