/*
* 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.
*/
package org.apache.jackrabbit.core.cache;
import static org.apache.jackrabbit.core.cache.CacheAccessListener.ACCESS_INTERVAL;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
/**
* Abstract base class for managed {@link Cache}s. This class uses atomic
* variables to track the current and maximum size of the cache, the cache
* access count and a possible {@link CacheAccessListener} instance.
* <p>
* A subclass should call the protected {@link #recordCacheAccess()} method
* whenever the cache is accessed (even cache misses should be reported).
* The subclass should also use the {@link #recordSizeChange(long)} method
* to record all changes in the cache size, and automatically evict excess
* items when the {@link #isTooBig()} method returns <code>true</code>.
*/
public abstract class AbstractCache implements Cache {
/**
* The estimated amount of memory currently used by this cache. The
* current value is returned by the {@link #getMemoryUsed()} method
* and can be updated by a subclass using the protected
* {@link #recordSizeChange(long)} method.
*/
private final AtomicLong memoryUsed = new AtomicLong();
/**
* The allocated maximum size of this cache. A {@link CacheManager} uses
* the {@link #getMaxMemorySize()} and {@link #setMaxMemorySize(long)}
* methods to control the target size of a cache. Subclasses can use the
* protected {@link #isTooBig()} method to determine whether the current
* size of the cache exceeds this size limit.
*/
private final AtomicLong maxMemorySize = new AtomicLong();
/**
* Cache access counter. Used to fire periodic
* {@link CacheAccessListener#cacheAccessed()} events once every
* {@link CacheAccessListener#ACCESS_INTERVAL} calls to the protected
* {@link #recordCacheAccess()} method.
* <p>
* A long counter is used to prevent integer overflow. Even if the cache
* was accessed once every nanosecond, an overflow would only occur in
* about 300 years. See
* <a href="https://issues.apache.org/jira/browse/JCR-3013">JCR-3013</a>.
*/
private final AtomicLong accessCount = new AtomicLong();
/**
* Cache access counter. Unike his counterpart {@link #accessCount}, this
* does not get reset.
*
* It is used in the cases where a cache listener needs to call
* {@link Cache#resetAccessCount()}, but also needs a total access count. If
* you are sure that nobody calls reset, you can just use
* {@link #accessCount}.
*/
private final AtomicLong totalAccessCount = new AtomicLong();
/**
* Cache miss counter.
*/
private final AtomicLong missCount = new AtomicLong();
/**
* Cache access listener. Set in the
* {@link #setAccessListener(CacheAccessListener)} method and accessed
* by periodically by the {@link #recordCacheAccess()} method.
*/
private final AtomicReference<CacheAccessListener> accessListener =
new AtomicReference<CacheAccessListener>();
/**
* Checks whether the current estimate of the amount of memory used
* by this cache exceeds the allocated maximum amount of memory.
*
* @return <code>true</code> if the cache size is too big,
* <code>false</code> otherwise
*/
protected boolean isTooBig() {
return memoryUsed.get() > maxMemorySize.get();
}
/**
* Updates the current memory use estimate of this cache.
*
* @param delta number of bytes added or removed
*/
protected void recordSizeChange(long delta) {
memoryUsed.addAndGet(delta); // ignore the return value
}
/**
* Records a single cache access and calls the configured
* {@link CacheAccessListener} (if any) whenever the constant access
* interval has passed since the previous listener call.
*/
protected void recordCacheAccess() {
totalAccessCount.incrementAndGet();
long count = accessCount.incrementAndGet();
if (count % ACCESS_INTERVAL == 0) {
CacheAccessListener listener = accessListener.get();
if (listener != null) {
listener.cacheAccessed(ACCESS_INTERVAL);
}
}
}
protected void recordCacheMiss() {
missCount.incrementAndGet();
}
public long getAccessCount() {
return accessCount.get();
}
public void resetAccessCount() {
accessCount.set(0);
}
public long getTotalAccessCount(){
return totalAccessCount.get();
}
public long getMissCount() {
return missCount.get();
}
public void resetMissCount() {
missCount.set(0);
}
public long getMemoryUsed() {
return memoryUsed.get();
}
public long getMaxMemorySize() {
return maxMemorySize.get();
}
public void setMaxMemorySize(long size) {
maxMemorySize.set(size);
}
/**
* Set the cache access listener. Only one listener per cache is supported.
*
* @param listener the new listener
*/
public void setAccessListener(CacheAccessListener listener) {
accessListener.set(listener);
}
/**
* {@inheritDoc}
*/
public void dispose() {
CacheAccessListener listener = accessListener.get();
if (listener != null) {
listener.disposeCache(this);
}
}
/**
* {@inheritDoc}
*/
public String getCacheInfoAsString() {
long u = getMemoryUsed() / 1024;
long m = getMaxMemorySize() / 1024;
StringBuilder c = new StringBuilder();
c.append("cachename=");
c.append(this.toString());
c.append(", elements=");
c.append(getElementCount());
c.append(", usedmemorykb=");
c.append(u);
c.append(", maxmemorykb=");
c.append(m);
c.append(", access=");
c.append(getTotalAccessCount());
c.append(", miss=");
c.append(getMissCount());
return c.toString();
}
}