001package org.apache.archiva.components.cache.hashmap; 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 org.apache.archiva.components.cache.AbstractCache; 023import org.apache.archiva.components.cache.AbstractCacheStatistics; 024import org.apache.archiva.components.cache.Cache; 025import org.apache.archiva.components.cache.CacheStatistics; 026import org.apache.archiva.components.cache.CacheableWrapper; 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029import org.springframework.stereotype.Service; 030 031import javax.annotation.PostConstruct; 032import java.util.Iterator; 033import java.util.LinkedHashMap; 034import java.util.Map; 035 036/** 037 * <p> 038 * HashMapCache - this is a Cache implementation taken from the Archiva project. 039 * </p> 040 * <p/> 041 * <p> 042 * Original class written by Edwin Punzalan for purposes of addressing the 043 * jira ticket <a href="http://jira.codehaus.org/browse/MRM-39">MRM-39</a> 044 * </p> 045 * <p> 046 * Configure the refreshTime in seconds value configure a ttl of object life in cache. 047 * Object get( Object key ) : 048 * <ul> 049 * <li> < 0 : method will always return null (no cache)</li> 050 * <li> = 0 : first stored object will be return (infinite life in the cache)</li> 051 * <li> > 0 : after a live (stored time) of refreshTime the object will be remove from the cache 052 * and a no object will be returned by the method</li> 053 * </ul> 054 * </p> 055 * 056 * @author Edwin Punzalan 057 * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a> 058 */ 059@Service( "cache#hashmap" ) 060public class HashMapCache<V, T> 061 extends AbstractCache<V, T> 062 implements Cache<V, T> 063{ 064 065 private Logger log = LoggerFactory.getLogger( getClass( ) ); 066 067 class Stats 068 extends AbstractCacheStatistics 069 implements CacheStatistics 070 { 071 072 public Stats( ) 073 { 074 super( ); 075 } 076 077 public long getSize( ) 078 { 079 synchronized (cache) 080 { 081 return cache.size( ); 082 } 083 } 084 085 } 086 087 private Map<V, CacheableWrapper<T>> cache; 088 089 /** 090 * 091 */ 092 private double cacheHitRatio = 1.0; 093 094 /** 095 * 096 */ 097 private int cacheMaxSize = 0; 098 099 /** 100 * 101 */ 102 private int refreshTime; 103 104 private Stats stats; 105 106 public HashMapCache( ) 107 { 108 // noop 109 } 110 111 /** 112 * Empty the cache and reset the cache hit rate 113 */ 114 public void clear( ) 115 { 116 synchronized (cache) 117 { 118 stats.clear( ); 119 cache.clear( ); 120 } 121 } 122 123 /** 124 * Check for a cached object and return it if it exists. Returns null when the keyed object is not found 125 * 126 * @param key the key used to map the cached object 127 * @return the object mapped to the given key, or null if no cache object is mapped to the given key 128 */ 129 public T get( V key ) 130 { 131 CacheableWrapper<T> retValue = null; 132 // prevent search 133 if ( !this.isCacheAvailable( ) ) 134 { 135 return null; 136 } 137 synchronized (cache) 138 { 139 if ( cache.containsKey( key ) ) 140 { 141 // remove and put: this promotes it to the top since we use a linked hash map 142 retValue = cache.remove( key ); 143 144 if ( needRefresh( retValue ) ) 145 { 146 stats.miss( ); 147 return null; 148 } 149 else 150 { 151 cache.put( key, retValue ); 152 stats.hit( ); 153 } 154 } 155 else 156 { 157 stats.miss( ); 158 } 159 } 160 161 return retValue == null ? null : retValue.getValue( ); 162 } 163 164 165 protected boolean needRefresh( CacheableWrapper cacheableWrapper ) 166 { 167 if ( cacheableWrapper == null ) 168 { 169 return true; 170 } 171 if ( this.getRefreshTime( ) == 0 ) 172 { 173 return false; 174 } 175 boolean result = 176 ( System.currentTimeMillis( ) - cacheableWrapper.getStoredTime( ) ) > ( this.getRefreshTime( ) * 1000 ); 177 178 log.debug( "{} is uptodate {}", cacheableWrapper, result ); 179 180 return result; 181 } 182 183 public CacheStatistics getStatistics( ) 184 { 185 return stats; 186 } 187 188 /** 189 * Check if the specified key is already mapped to an object. 190 * 191 * @param key the key used to map the cached object 192 * @return true if the cache contains an object associated with the given key 193 */ 194 public boolean hasKey( V key ) 195 { 196 // prevent search 197 if ( !this.isCacheAvailable( ) ) 198 { 199 return false; 200 } 201 boolean contains; 202 synchronized (cache) 203 { 204 contains = cache.containsKey( key ); 205 206 if ( contains ) 207 { 208 stats.hit( ); 209 } 210 else 211 { 212 stats.miss( ); 213 } 214 } 215 216 return contains; 217 } 218 219 @PostConstruct 220 public void initialize( ) 221 { 222 stats = new Stats( ); 223 224 if ( cacheMaxSize > 0 ) 225 { 226 cache = new LinkedHashMap<>( cacheMaxSize ); 227 } 228 else 229 { 230 cache = new LinkedHashMap<>( ); 231 } 232 } 233 234 /** 235 * Cache the given value and map it using the given key 236 * 237 * @param key the object to map the valued object 238 * @param value the object to cache 239 */ 240 public T put( V key, T value ) 241 { 242 CacheableWrapper<T> ret = null; 243 244 // remove and put: this promotes it to the top since we use a linked hash map 245 synchronized (cache) 246 { 247 if ( cache.containsKey( key ) ) 248 { 249 cache.remove( key ); 250 } 251 252 ret = cache.put( key, new CacheableWrapper<>( value, System.currentTimeMillis( ) ) ); 253 } 254 255 manageCache( ); 256 257 return ret == null ? null : ret.getValue( ); 258 } 259 260 /** 261 * Cache the given value and map it using the given key 262 * 263 * @param key the object to map the valued object 264 * @param value the object to cache 265 */ 266 public void register( V key, T value ) 267 { 268 // remove and put: this promotes it to the top since we use a linked hash map 269 synchronized (cache) 270 { 271 if ( cache.containsKey( key ) ) 272 { 273 cache.remove( key ); 274 } 275 276 cache.put( key, new CacheableWrapper<>( value, System.currentTimeMillis( ) ) ); 277 } 278 279 manageCache( ); 280 } 281 282 public T remove( V key ) 283 { 284 synchronized (cache) 285 { 286 if ( cache.containsKey( key ) ) 287 { 288 return cache.remove( key ).getValue( ); 289 } 290 } 291 292 return null; 293 } 294 295 private void manageCache( ) 296 { 297 synchronized (cache) 298 { 299 Iterator iterator = cache.entrySet( ).iterator( ); 300 if ( cacheMaxSize == 0 ) 301 { 302 // desired HitRatio is reached, we can trim the cache to conserve memory 303 if ( cacheHitRatio <= stats.getCacheHitRate( ) ) 304 { 305 iterator.next( ); 306 iterator.remove( ); 307 } 308 } 309 else if ( cache.size( ) > cacheMaxSize ) 310 { 311 // maximum cache size is reached 312 while ( cache.size( ) > cacheMaxSize ) 313 { 314 iterator.next( ); 315 iterator.remove( ); 316 } 317 } 318 else 319 { 320 // even though the max has not been reached, the desired HitRatio is already reached, 321 // so we can trim the cache to conserve memory 322 if ( cacheHitRatio <= stats.getCacheHitRate( ) ) 323 { 324 iterator.next( ); 325 iterator.remove( ); 326 } 327 } 328 } 329 } 330 331 332 public int getRefreshTime( ) 333 { 334 return refreshTime; 335 } 336 337 /** 338 * 339 */ 340 public void setRefreshTime( int refreshTime ) 341 { 342 this.refreshTime = refreshTime; 343 } 344 345 /** 346 * @return true, if the cache is available, otherwise false 347 */ 348 protected boolean isCacheAvailable( ) 349 { 350 return this.getRefreshTime( ) >= 0; 351 } 352 353 public double getCacheHitRatio( ) 354 { 355 return cacheHitRatio; 356 } 357 358 public void setCacheHitRatio( double cacheHitRatio ) 359 { 360 this.cacheHitRatio = cacheHitRatio; 361 } 362 363 public int getCacheMaxSize( ) 364 { 365 return cacheMaxSize; 366 } 367 368 public void setCacheMaxSize( int cacheMaxSize ) 369 { 370 this.cacheMaxSize = cacheMaxSize; 371 } 372 373 public Stats getStats( ) 374 { 375 return stats; 376 } 377}