/*
* 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;
}
StringBuffer statistics = new StringBuffer();
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;
}
}
}