/* * 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.state; import org.apache.commons.collections.map.ReferenceMap; import org.apache.jackrabbit.core.id.ItemId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * <code>ItemStateReferenceCache</code> internally consists of 2 components: * <ul> * <li>an <code>ItemStateReferenceMap</code> serving as the primary (or main) * cache; it holds weak references to <code>ItemState</code> instances. This * <code>ItemStateCache</code> implementation directly represents the * contents of the primary cache, i.e. {@link #isCached(ItemId)}, * {@link #retrieve(ItemId)}}, {@link #isEmpty()} etc. only refer to the contents * of the primary cache.</li> * <li>an <code>ItemStateCache</code> implementing a custom eviction policy and * serving as the secondary (or auxiliary) cache; entries that are automatically * flushed from this secondary cache through its eviction policy (LRU, etc.) * will be indirectly flushed from the primary (reference) cache by the garbage * collector if they are thus rendered weakly reachable. * </li> * </ul> * <p> * This implementation of ItemStateCache is thread-safe. */ public class ItemStateReferenceCache implements ItemStateCache { /** Logger instance */ private static final Logger log = LoggerFactory.getLogger(ItemStateReferenceCache.class); /** * The number of cache segments to use. Use the number of available * processors (even if that might change during runtime!) as a reasonable * approximation of the amount of parallelism we should expect in the * worst case. * <p> * One reason for this value being a constant is that the * {@link Runtime#availableProcessors()} call is somewhat expensive at * least in some environments. */ private static int NUMBER_OF_SEGMENTS = Runtime.getRuntime().availableProcessors(); /** * Cache that automatically flushes entries based on some eviction policy; * entries flushed from the secondary cache will be indirectly flushed * from the reference map by the garbage collector if they thus are * rendered weakly reachable. */ private final ItemStateCache cache; /** * Segments of the weak reference map used to keep track of item states. */ private final Map<ItemId, ItemState>[] segments; /** * Creates a new <code>ItemStateReferenceCache</code> that uses a * <code>MLRUItemStateCache</code> instance as internal cache. */ public ItemStateReferenceCache(ItemStateCacheFactory cacheFactory) { this(cacheFactory.newItemStateCache()); } /** * Creates a new <code>ItemStateReferenceCache</code> that uses the * specified <code>ItemStateCache</code> instance as internal secondary * cache. * * @param cache secondary cache implementing a custom eviction policy */ @SuppressWarnings("unchecked") public ItemStateReferenceCache(ItemStateCache cache) { this.cache = cache; this.segments = new Map[NUMBER_OF_SEGMENTS]; for (int i = 0; i < segments.length; i++) { // I tried using soft instead of weak references here, but that // seems to have some unexpected performance consequences (notable // increase in the JCR TCK run time). So even though soft references // are generally recommended over weak references for caching // purposes, it seems that using weak references is safer here. segments[i] = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK); } } /** * Returns the reference map segment for the given entry key. The segment * is selected based on the hash code of the key, after a transformation * to prevent interfering with the optimal performance of the segment * hash map. * * @param id item identifier * @return reference map segment */ private Map<ItemId, ItemState> getSegment(ItemId id) { // Unsigned shift right to prevent negative indexes and to // prevent too similar keys to all get stored in the same segment return segments[(id.hashCode() >>> 1) % segments.length]; } //-------------------------------------------------------< ItemStateCache > /** * {@inheritDoc} */ public boolean isCached(ItemId id) { Map<ItemId, ItemState> segment = getSegment(id); synchronized (segment) { return segment.containsKey(id); } } /** * {@inheritDoc} */ public ItemState retrieve(ItemId id) { // Update the access statistics in the cache ItemState state = cache.retrieve(id); if (state != null) { // Return fast to avoid the second lookup below return state; } Map<ItemId, ItemState> segment = getSegment(id); synchronized (segment) { return segment.get(id); } } /** * {@inheritDoc} */ public ItemState[] retrieveAll() { List<ItemState> states = new ArrayList<ItemState>(); for (int i = 0; i < segments.length; i++) { synchronized (segments[i]) { states.addAll(segments[i].values()); } } return states.toArray(new ItemState[states.size()]); } /** * {@inheritDoc} */ public void cache(ItemState state) { // Update the cache cache.cache(state); // Store a weak reference in the reference map ItemId id = state.getId(); Map<ItemId, ItemState> segment = getSegment(id); synchronized (segment) { ItemState s = segment.put(id, state); // overwriting the same instance is OK if (s != null && s != state) { log.warn("overwriting cached entry " + id); } } } /** * {@inheritDoc} */ public void evict(ItemId id) { // Update the cache cache.evict(id); // Remove from reference map // TODO: Allow the weak reference to be cleared automatically? Map<ItemId, ItemState> segment = getSegment(id); synchronized (segment) { segment.remove(id); } } /** * {@inheritDoc} */ public void dispose() { cache.dispose(); } /** * {@inheritDoc} */ public void evictAll() { // Update the cache cache.evictAll(); // remove all weak references from reference map // TODO: Allow the weak reference to be cleared automatically? for (int i = 0; i < segments.length; i++) { synchronized (segments[i]) { segments[i].clear(); } } } /** * {@inheritDoc} */ public boolean isEmpty() { for (int i = 0; i < segments.length; i++) { synchronized (segments[i]) { if (!segments[i].isEmpty()) { return false; } } } return true; } }