blob: f0084f6ed1c5eb900f85fb0f277723fa0d229f90 [file] [log] [blame]
/*
* 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.oak.plugins.document.persistentCache;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import static com.google.common.collect.Maps.newConcurrentMap;
/**
* In order to avoid leaking values from the metadataMap, following order should
* be maintained for combining the cache and CacheMetadata:
*
* 1. For remove(), removeAll() and clear():
*
* - cache.invalidate()
* - metadata.remove()
*
* 2. For put(), putAll() and putFromPersistenceAndIncrement():
*
* - metadata.put()
* - cache.put()
*
* 3. For increment():
*
* - metadata.increment()
* - cache.get()
* - (metadata.remove() if value doesn't exists in cache)
*
* 4. For incrementAll():
*
* - metadata.incrementAll()
* - cache.getAll()
* - (metadata.removeAll() on keys that returned nulls)
*
* Preserving this order will allow to avoid leaked values in the metadata without
* an extra synchronization between cache and metadata operations. This strategy
* is a best-effort option - it may happen that cache values won't have their
* metadata entries.
*/
public class CacheMetadata<K> {
private final ConcurrentMap<K, MetadataEntry> metadataMap = newConcurrentMap();
private boolean enabled = true;
boolean isEnabled() {
return enabled;
}
void disable() {
this.enabled = false;
}
void put(K key) {
if (!enabled) {
return;
}
getOrCreate(key, false);
}
void putFromPersistenceAndIncrement(K key) {
if (!enabled) {
return;
}
getOrCreate(key, true).incrementCount();
}
void increment(K key) {
if (!enabled) {
return;
}
getOrCreate(key, false).incrementCount();
}
MetadataEntry remove(Object key) {
if (!enabled) {
return null;
}
return metadataMap.remove(key);
}
void putAll(Iterable<?> keys) {
if (!enabled) {
return;
}
for (Object k : keys) {
getOrCreate((K) k, false);
}
}
void incrementAll(Iterable<?> keys) {
if (!enabled) {
return;
}
for (Object k : keys) {
getOrCreate((K) k, false).incrementCount();
}
}
void removeAll(Iterable<?> keys) {
if (!enabled) {
return;
}
for (Object k : keys) {
metadataMap.remove(k);
}
}
void clear() {
if (!enabled) {
return;
}
metadataMap.clear();
}
private MetadataEntry getOrCreate(K key, boolean readFromPersistentCache) {
if (!enabled) {
return null;
}
MetadataEntry metadata = metadataMap.get(key);
if (metadata == null) {
MetadataEntry newEntry = new MetadataEntry(readFromPersistentCache);
MetadataEntry oldEntry = metadataMap.putIfAbsent(key, newEntry);
metadata = oldEntry == null ? newEntry : oldEntry;
}
return metadata;
}
static class MetadataEntry {
private final AtomicLong accessCount = new AtomicLong();
private final boolean readFromPersistentCache;
private MetadataEntry(boolean readFromPersistentCache) {
this.readFromPersistentCache = readFromPersistentCache;
}
void incrementCount() {
accessCount.incrementAndGet();
}
long getAccessCount() {
return accessCount.get();
}
boolean isReadFromPersistentCache() {
return readFromPersistentCache;
}
}
}