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}