/**
* 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.blur.lucene.search;
import static org.apache.blur.metrics.MetricsConstants.DEEP_PAGING_CACHE;
import static org.apache.blur.metrics.MetricsConstants.EVICTION;
import static org.apache.blur.metrics.MetricsConstants.HIT;
import static org.apache.blur.metrics.MetricsConstants.MISS;
import static org.apache.blur.metrics.MetricsConstants.ORG_APACHE_BLUR;
import static org.apache.blur.metrics.MetricsConstants.SIZE;
import java.lang.ref.WeakReference;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.TimeUnit;
import org.apache.blur.log.Log;
import org.apache.blur.log.LogFactory;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
import com.googlecode.concurrentlinkedhashmap.EvictionListener;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.Gauge;
import com.yammer.metrics.core.Meter;
import com.yammer.metrics.core.MetricName;
public class DeepPagingCache {
private final static Log LOG = LogFactory.getLog(DeepPagingCache.class);
private static final long DEFAULT_MAX = 1024;
private final ConcurrentNavigableMap<DeepPageKeyPlusPosition, DeepPageContainer> _positionCache;
private final ConcurrentLinkedHashMap<DeepPageKeyPlusPosition, DeepPageContainer> _lruCache;
private final Meter _hits;
private final Meter _misses;
private final Meter _evictions;
public DeepPagingCache() {
this(DEFAULT_MAX);
}
public DeepPagingCache(long maxEntriesForDeepPaging) {
_hits = Metrics.newMeter(new MetricName(ORG_APACHE_BLUR, DEEP_PAGING_CACHE, HIT), HIT, TimeUnit.SECONDS);
_misses = Metrics.newMeter(new MetricName(ORG_APACHE_BLUR, DEEP_PAGING_CACHE, MISS), MISS, TimeUnit.SECONDS);
_evictions = Metrics.newMeter(new MetricName(ORG_APACHE_BLUR, DEEP_PAGING_CACHE, EVICTION), EVICTION,
TimeUnit.SECONDS);
_lruCache = new ConcurrentLinkedHashMap.Builder<DeepPageKeyPlusPosition, DeepPageContainer>()
.maximumWeightedCapacity(maxEntriesForDeepPaging)
.listener(new EvictionListener<DeepPageKeyPlusPosition, DeepPageContainer>() {
@Override
public void onEviction(DeepPageKeyPlusPosition key, DeepPageContainer value) {
_positionCache.remove(key);
_evictions.mark();
}
}).build();
Metrics.newGauge(new MetricName(ORG_APACHE_BLUR, DEEP_PAGING_CACHE, SIZE), new Gauge<Long>() {
@Override
public Long value() {
return _lruCache.weightedSize();
}
});
_positionCache = new ConcurrentSkipListMap<DeepPageKeyPlusPosition, DeepPageContainer>();
}
public void add(DeepPageKey key, DeepPageContainer deepPageContainer) {
LOG.debug("Adding DeepPageContainer [{0}] for key [{1}]", deepPageContainer, key);
DeepPageKeyPlusPosition k = new DeepPageKeyPlusPosition(deepPageContainer.position, key);
_lruCache.put(k, deepPageContainer);
_positionCache.put(k, deepPageContainer);
}
public DeepPageContainer lookup(DeepPageKey key, int skipTo) {
LOG.debug("Looking up DeepPageContainer for skipTo [{0}] with key hashcode [{1}] and key [{2}]", skipTo,
key.hashCode(), key);
DeepPageKeyPlusPosition k = new DeepPageKeyPlusPosition(skipTo, key);
DeepPageContainer deepPageContainer = _lruCache.get(k);
if (deepPageContainer != null) {
_hits.mark();
return deepPageContainer;
}
ConcurrentNavigableMap<DeepPageKeyPlusPosition, DeepPageContainer> headMap = _positionCache.headMap(k, true);
if (headMap == null || headMap.isEmpty()) {
_misses.mark();
return null;
}
Entry<DeepPageKeyPlusPosition, DeepPageContainer> firstEntry = headMap.lastEntry();
DeepPageKeyPlusPosition dpkpp = firstEntry.getKey();
if (dpkpp._deepPageKey.equals(key)) {
_hits.mark();
return firstEntry.getValue();
}
_misses.mark();
return null;
}
static class DeepPageKeyWithPosition {
final DeepPageKey _deepPageKey;
final int _position;
DeepPageKeyWithPosition(DeepPageKey deepPageKey, int position) {
_deepPageKey = deepPageKey;
_position = position;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((_deepPageKey == null) ? 0 : _deepPageKey.hashCode());
result = prime * result + _position;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DeepPageKeyWithPosition other = (DeepPageKeyWithPosition) obj;
if (_deepPageKey == null) {
if (other._deepPageKey != null)
return false;
} else if (!_deepPageKey.equals(other._deepPageKey))
return false;
if (_position != other._position)
return false;
return true;
}
}
static class DeepPageKeyPlusPosition implements Comparable<DeepPageKeyPlusPosition> {
final int _position;
final DeepPageKey _deepPageKey;
DeepPageKeyPlusPosition(int position, DeepPageKey deepPageKey) {
_position = position;
_deepPageKey = deepPageKey;
}
@Override
public int compareTo(DeepPageKeyPlusPosition o) {
int compareTo = _deepPageKey.compareTo(o._deepPageKey);
if (compareTo == 0) {
return _position - o._position;
}
return compareTo;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((_deepPageKey == null) ? 0 : _deepPageKey.hashCode());
result = prime * result + _position;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DeepPageKeyPlusPosition other = (DeepPageKeyPlusPosition) obj;
if (_deepPageKey == null) {
if (other._deepPageKey != null)
return false;
} else if (!_deepPageKey.equals(other._deepPageKey))
return false;
if (_position != other._position)
return false;
return true;
}
}
public static class DeepPageKey implements Comparable<DeepPageKey> {
public DeepPageKey(Query q, Sort s, Object o) {
_indexKey = new WeakReference<Object>(o);
_sort = s;
_query = q;
}
final WeakReference<Object> _indexKey;
final Query _query;
final Sort _sort;
@Override
public int compareTo(DeepPageKey o) {
return hashCode() - o.hashCode();
}
private Object getIndexKey() {
return _indexKey.get();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getIndexKey() == null) ? 0 : getIndexKey().hashCode());
result = prime * result + ((_query == null) ? 0 : _query.hashCode());
result = prime * result + ((_sort == null) ? 0 : _sort.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DeepPageKey other = (DeepPageKey) obj;
if (getIndexKey() == null) {
if (other.getIndexKey() != null)
return false;
} else if (!getIndexKey().equals(other.getIndexKey()))
return false;
if (_query == null) {
if (other._query != null)
return false;
} else if (!_query.equals(other._query))
return false;
if (_sort == null) {
if (other._sort != null)
return false;
} else if (!_sort.equals(other._sort))
return false;
return true;
}
@Override
public String toString() {
return "DeepPageKey [_indexKey=" + getIndexKey() + ", _query=" + _query + ", _sort=" + _sort + "]";
}
}
public static class DeepPageContainer {
int position;
ScoreDoc scoreDoc;
@Override
public String toString() {
return "DeepPageContainer [position=" + position + ", scoreDoc=" + scoreDoc + "]";
}
}
}