001package org.apache.commons.jcs.engine.memory.soft;
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.lang.ref.SoftReference;
024import java.util.HashSet;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.concurrent.ConcurrentHashMap;
030import java.util.concurrent.ConcurrentMap;
031import java.util.concurrent.LinkedBlockingQueue;
032
033import org.apache.commons.jcs.engine.CacheConstants;
034import org.apache.commons.jcs.engine.behavior.ICacheElement;
035import org.apache.commons.jcs.engine.behavior.ICompositeCacheAttributes;
036import org.apache.commons.jcs.engine.control.CompositeCache;
037import org.apache.commons.jcs.engine.control.group.GroupAttrName;
038import org.apache.commons.jcs.engine.memory.AbstractMemoryCache;
039import org.apache.commons.jcs.engine.memory.util.MemoryElementDescriptor;
040import org.apache.commons.jcs.engine.memory.util.SoftReferenceElementDescriptor;
041import org.apache.commons.jcs.engine.stats.StatElement;
042import org.apache.commons.jcs.engine.stats.behavior.IStatElement;
043import org.apache.commons.jcs.engine.stats.behavior.IStats;
044import org.apache.commons.logging.Log;
045import org.apache.commons.logging.LogFactory;
046
047/**
048 * A JCS IMemoryCache that has {@link SoftReference} to all its values.
049 * This cache does not respect {@link ICompositeCacheAttributes#getMaxObjects()}
050 * as overflowing is handled by Java GC.
051 * <p>
052 * The cache also has strong references to a maximum number of objects given by
053 * the maxObjects parameter
054 *
055 * @author halset
056 */
057public class SoftReferenceMemoryCache<K, V> extends AbstractMemoryCache<K, V>
058{
059    /** The logger. */
060    private static final Log log = LogFactory.getLog(SoftReferenceMemoryCache.class);
061
062    /**
063     * Strong references to the maxObjects number of newest objects.
064     * <p>
065     * Trimming is done by {@link #trimStrongReferences()} instead of by
066     * overriding removeEldestEntry to be able to control waterfalling as easy
067     * as possible
068     */
069    private LinkedBlockingQueue<ICacheElement<K, V>> strongReferences;
070
071    /**
072     * For post reflection creation initialization
073     * <p>
074     * @param hub
075     */
076    @Override
077    public synchronized void initialize( CompositeCache<K, V> hub )
078    {
079        super.initialize( hub );
080        strongReferences = new LinkedBlockingQueue<ICacheElement<K, V>>();
081        log.info( "initialized Soft Reference Memory Cache for " + getCacheName() );
082    }
083
084    /**
085     * @see org.apache.commons.jcs.engine.memory.AbstractMemoryCache#createMap()
086     */
087    @Override
088    public ConcurrentMap<K, MemoryElementDescriptor<K, V>> createMap()
089    {
090        return new ConcurrentHashMap<K, MemoryElementDescriptor<K, V>>();
091    }
092
093    /**
094     * @see org.apache.commons.jcs.engine.memory.behavior.IMemoryCache#getKeySet()
095     */
096    @Override
097    public Set<K> getKeySet()
098    {
099        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}