/* * 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.exoplatform.services.jcr.impl.core.query.lucene; import org.apache.commons.collections.map.LRUMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implements a Document number cache with a fixed size and a LRU strategy. */ final class DocNumberCache { /** * Logger instance for this class. */ private static final Logger log = LoggerFactory.getLogger("exo.jcr.component.core.DocNumberCache"); /** * Log cache statistics at most every 10 seconds. */ private static final long LOG_INTERVAL = 1000 * 10; /** * The number of cache segments. */ private static final int CACHE_SEGMENTS = 0x10; /** * Mask to calculate segment number. */ private static final int CACHE_SEGMENTS_MASK = CACHE_SEGMENTS - 1; /** * LRU Maps where key=uuid value=reader;docNumber */ private final LRUMap[] docNumbers = new LRUMap[CACHE_SEGMENTS]; /** * Timestamp of the last cache statistics log. */ private long lastLog; /** * Cache misses. */ private long misses; /** * Cache accesses; */ private long accesses; /** * Creates a new <code>DocNumberCache</code> with a limiting * <code>size</code>. * * @param size the cache limit. */ DocNumberCache(int size) { size = size / CACHE_SEGMENTS; if (size < 0x40) { // minimum size is 0x40 * 0x10 = 1024 size = 0x40; } for (int i = 0; i < docNumbers.length; i++) { docNumbers[i] = new LRUMap(size); } } /** * Puts a document number into the cache using a uuid as key. An entry is * only overwritten if the according reader is younger than the reader * associated with the existing entry. * * @param uuid the key. * @param reader the index reader from where the document number was read. * @param n the document number. */ void put(String uuid, CachingIndexReader reader, int n) { LRUMap cacheSegment = docNumbers[getSegmentIndex(uuid.charAt(0))]; //UUID key = UUID.fromString(uuid); String key = uuid; synchronized (cacheSegment) { Entry e = (Entry)cacheSegment.get(key); if (e != null) { // existing entry // ignore if reader is older than the one in entry if (reader.getCreationTick() <= e.creationTick) { if (log.isDebugEnabled()) { log.debug("Ignoring put(). New entry is not from a newer reader. " + "existing: " + e.creationTick + ", new: " + reader.getCreationTick()); } e = null; } } else { // entry did not exist e = new Entry(reader.getCreationTick(), n); } if (e != null) { cacheSegment.put(key, e); } } } /** * Returns the cache entry for <code>uuid</code>, or <code>null</code> if * no entry exists for <code>uuid</code>. * * @param uuid the key. * @return cache entry or <code>null</code>. */ Entry get(String uuid) { LRUMap cacheSegment = docNumbers[getSegmentIndex(uuid.charAt(0))]; // uuid may be invalid // if (uuid.length() != UUID.UUID_FORMATTED_LENGTH) { // return null; // } String key = uuid; Entry entry; synchronized (cacheSegment) { entry = (Entry)cacheSegment.get(key); } if (log.isDebugEnabled()) { accesses++; if (entry == null) { misses++; } // log at most after 1000 accesses and every 10 seconds if (accesses > 1000 && System.currentTimeMillis() - lastLog > LOG_INTERVAL) { long ratio = 100; if (misses != 0) { ratio -= misses * 100L / accesses; } StringBuilder statistics = new StringBuilder(); int inUse = 0; for (int i = 0; i < docNumbers.length; i++) { inUse += docNumbers[i].size(); } statistics.append("size=").append(inUse); statistics.append("/").append(docNumbers[0].maxSize() * CACHE_SEGMENTS); statistics.append(", #accesses=").append(accesses); statistics.append(", #hits=").append((accesses - misses)); statistics.append(", #misses=").append(misses); statistics.append(", cacheRatio=").append(ratio).append("%"); log.debug(statistics.toString()); accesses = 0; misses = 0; lastLog = System.currentTimeMillis(); } } return entry; } /** * Returns the segment index for character c. */ private static int getSegmentIndex(char c) { if (c > '9') { c += 9; } return c & CACHE_SEGMENTS_MASK; } public static final class Entry { /** * The creation tick of the IndexReader. */ final long creationTick; /** * The document number. */ final int doc; Entry(long creationTick, int doc) { this.creationTick = creationTick; this.doc = doc; } } }