| package org.apache.archiva.components.cache.hashmap; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import org.apache.archiva.components.cache.AbstractCache; |
| import org.apache.archiva.components.cache.AbstractCacheStatistics; |
| import org.apache.archiva.components.cache.Cache; |
| import org.apache.archiva.components.cache.CacheStatistics; |
| import org.apache.archiva.components.cache.CacheableWrapper; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.springframework.stereotype.Service; |
| |
| import jakarta.annotation.PostConstruct; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| |
| /** |
| * <p> |
| * HashMapCache - this is a Cache implementation taken from the Archiva project. |
| * </p> |
| * <p/> |
| * <p> |
| * Original class written by Edwin Punzalan for purposes of addressing the |
| * jira ticket <a href="http://jira.codehaus.org/browse/MRM-39">MRM-39</a> |
| * </p> |
| * <p> |
| * Configure the refreshTime in seconds value configure a ttl of object life in cache. |
| * Object get( Object key ) : |
| * <ul> |
| * <li> < 0 : method will always return null (no cache)</li> |
| * <li> = 0 : first stored object will be return (infinite life in the cache)</li> |
| * <li> > 0 : after a live (stored time) of refreshTime the object will be remove from the cache |
| * and a no object will be returned by the method</li> |
| * </ul> |
| * </p> |
| * |
| * @author Edwin Punzalan |
| * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a> |
| */ |
| @Service( "cache#hashmap" ) |
| public class HashMapCache<V, T> |
| extends AbstractCache<V, T> |
| implements Cache<V, T> |
| { |
| |
| private Logger log = LoggerFactory.getLogger( getClass( ) ); |
| |
| class Stats |
| extends AbstractCacheStatistics |
| implements CacheStatistics |
| { |
| |
| public Stats( ) |
| { |
| super( ); |
| } |
| |
| public long getSize( ) |
| { |
| synchronized (cache) |
| { |
| return cache.size( ); |
| } |
| } |
| |
| } |
| |
| private Map<V, CacheableWrapper<T>> cache; |
| |
| /** |
| * |
| */ |
| private double cacheHitRatio = 1.0; |
| |
| /** |
| * |
| */ |
| private int cacheMaxSize = 0; |
| |
| /** |
| * |
| */ |
| private int refreshTime; |
| |
| private Stats stats; |
| |
| public HashMapCache( ) |
| { |
| // noop |
| } |
| |
| /** |
| * Empty the cache and reset the cache hit rate |
| */ |
| public void clear( ) |
| { |
| synchronized (cache) |
| { |
| stats.clear( ); |
| cache.clear( ); |
| } |
| } |
| |
| /** |
| * Check for a cached object and return it if it exists. Returns null when the keyed object is not found |
| * |
| * @param key the key used to map the cached object |
| * @return the object mapped to the given key, or null if no cache object is mapped to the given key |
| */ |
| public T get( V key ) |
| { |
| CacheableWrapper<T> retValue = null; |
| // prevent search |
| if ( !this.isCacheAvailable( ) ) |
| { |
| return null; |
| } |
| synchronized (cache) |
| { |
| if ( cache.containsKey( key ) ) |
| { |
| // remove and put: this promotes it to the top since we use a linked hash map |
| retValue = cache.remove( key ); |
| |
| if ( needRefresh( retValue ) ) |
| { |
| stats.miss( ); |
| return null; |
| } |
| else |
| { |
| cache.put( key, retValue ); |
| stats.hit( ); |
| } |
| } |
| else |
| { |
| stats.miss( ); |
| } |
| } |
| |
| return retValue == null ? null : retValue.getValue( ); |
| } |
| |
| |
| protected boolean needRefresh( CacheableWrapper cacheableWrapper ) |
| { |
| if ( cacheableWrapper == null ) |
| { |
| return true; |
| } |
| if ( this.getRefreshTime( ) == 0 ) |
| { |
| return false; |
| } |
| boolean result = |
| ( System.currentTimeMillis( ) - cacheableWrapper.getStoredTime( ) ) > ( this.getRefreshTime( ) * 1000 ); |
| |
| log.debug( "{} is uptodate {}", cacheableWrapper, result ); |
| |
| return result; |
| } |
| |
| public CacheStatistics getStatistics( ) |
| { |
| return stats; |
| } |
| |
| /** |
| * Check if the specified key is already mapped to an object. |
| * |
| * @param key the key used to map the cached object |
| * @return true if the cache contains an object associated with the given key |
| */ |
| public boolean hasKey( V key ) |
| { |
| // prevent search |
| if ( !this.isCacheAvailable( ) ) |
| { |
| return false; |
| } |
| boolean contains; |
| synchronized (cache) |
| { |
| contains = cache.containsKey( key ); |
| |
| if ( contains ) |
| { |
| stats.hit( ); |
| } |
| else |
| { |
| stats.miss( ); |
| } |
| } |
| |
| return contains; |
| } |
| |
| @PostConstruct |
| public void initialize( ) |
| { |
| stats = new Stats( ); |
| |
| if ( cacheMaxSize > 0 ) |
| { |
| cache = new LinkedHashMap<>( cacheMaxSize ); |
| } |
| else |
| { |
| cache = new LinkedHashMap<>( ); |
| } |
| } |
| |
| /** |
| * Cache the given value and map it using the given key |
| * |
| * @param key the object to map the valued object |
| * @param value the object to cache |
| */ |
| public T put( V key, T value ) |
| { |
| CacheableWrapper<T> ret = null; |
| |
| // remove and put: this promotes it to the top since we use a linked hash map |
| synchronized (cache) |
| { |
| if ( cache.containsKey( key ) ) |
| { |
| cache.remove( key ); |
| } |
| |
| ret = cache.put( key, new CacheableWrapper<>( value, System.currentTimeMillis( ) ) ); |
| } |
| |
| manageCache( ); |
| |
| return ret == null ? null : ret.getValue( ); |
| } |
| |
| /** |
| * Cache the given value and map it using the given key |
| * |
| * @param key the object to map the valued object |
| * @param value the object to cache |
| */ |
| public void register( V key, T value ) |
| { |
| // remove and put: this promotes it to the top since we use a linked hash map |
| synchronized (cache) |
| { |
| if ( cache.containsKey( key ) ) |
| { |
| cache.remove( key ); |
| } |
| |
| cache.put( key, new CacheableWrapper<>( value, System.currentTimeMillis( ) ) ); |
| } |
| |
| manageCache( ); |
| } |
| |
| public T remove( V key ) |
| { |
| synchronized (cache) |
| { |
| if ( cache.containsKey( key ) ) |
| { |
| return cache.remove( key ).getValue( ); |
| } |
| } |
| |
| return null; |
| } |
| |
| private void manageCache( ) |
| { |
| synchronized (cache) |
| { |
| Iterator iterator = cache.entrySet( ).iterator( ); |
| if ( cacheMaxSize == 0 ) |
| { |
| // desired HitRatio is reached, we can trim the cache to conserve memory |
| if ( cacheHitRatio <= stats.getCacheHitRate( ) ) |
| { |
| iterator.next( ); |
| iterator.remove( ); |
| } |
| } |
| else if ( cache.size( ) > cacheMaxSize ) |
| { |
| // maximum cache size is reached |
| while ( cache.size( ) > cacheMaxSize ) |
| { |
| iterator.next( ); |
| iterator.remove( ); |
| } |
| } |
| else |
| { |
| // even though the max has not been reached, the desired HitRatio is already reached, |
| // so we can trim the cache to conserve memory |
| if ( cacheHitRatio <= stats.getCacheHitRate( ) ) |
| { |
| iterator.next( ); |
| iterator.remove( ); |
| } |
| } |
| } |
| } |
| |
| |
| public int getRefreshTime( ) |
| { |
| return refreshTime; |
| } |
| |
| /** |
| * |
| */ |
| public void setRefreshTime( int refreshTime ) |
| { |
| this.refreshTime = refreshTime; |
| } |
| |
| /** |
| * @return true, if the cache is available, otherwise false |
| */ |
| protected boolean isCacheAvailable( ) |
| { |
| return this.getRefreshTime( ) >= 0; |
| } |
| |
| public double getCacheHitRatio( ) |
| { |
| return cacheHitRatio; |
| } |
| |
| public void setCacheHitRatio( double cacheHitRatio ) |
| { |
| this.cacheHitRatio = cacheHitRatio; |
| } |
| |
| public int getCacheMaxSize( ) |
| { |
| return cacheMaxSize; |
| } |
| |
| public void setCacheMaxSize( int cacheMaxSize ) |
| { |
| this.cacheMaxSize = cacheMaxSize; |
| } |
| |
| public Stats getStats( ) |
| { |
| return stats; |
| } |
| } |