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<K, V> 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<K, V> 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}