/**
* 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.server.cache;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.blur.BlurConfiguration;
import org.apache.blur.manager.IndexServer;
import org.apache.blur.server.FilteredBlurServer;
import org.apache.blur.thirdparty.thrift_0_9_0.TException;
import org.apache.blur.thrift.BException;
import org.apache.blur.thrift.generated.Blur.Iface;
import org.apache.blur.thrift.generated.BlurException;
import org.apache.blur.thrift.generated.BlurQuery;
import org.apache.blur.thrift.generated.BlurResults;
import org.apache.blur.thrift.generated.FetchResult;
import org.apache.blur.thrift.generated.Selector;
import org.apache.blur.thrift.generated.TableStats;
import org.apache.blur.utils.ShardUtil;
public class ThriftCacheServer extends FilteredBlurServer {
private final ThriftCache _thriftCache;
private final IndexServer _indexServer;
private final ConcurrentMap<ThriftCacheKey<?>, Lock> _lockMap = new ConcurrentHashMap<ThriftCacheKey<?>, Lock>();
public ThriftCacheServer(BlurConfiguration configuration, Iface iface, IndexServer indexServer,
ThriftCache thriftCache) {
super(configuration, iface, true);
_thriftCache = thriftCache;
_indexServer = indexServer;
}
@Override
public void loadIndex(String table, List<String> externalIndexPaths) throws BlurException, TException {
try {
super.loadIndex(table, externalIndexPaths);
} finally {
_thriftCache.clearTable(table);
}
}
@Override
public TableStats tableStats(String table) throws BlurException, TException {
ThriftCacheKey<TableStats> key = _thriftCache.getKey(table, getShards(table), null, TableStats.class);
Lock lock = getOrCreateLock(key);
try {
lock.lock();
TableStats results = _thriftCache.get(key, TableStats.class);
if (results != null) {
return results;
}
return _thriftCache.put(key, super.tableStats(table));
} finally {
lock.unlock();
}
}
@Override
public BlurResults query(String table, BlurQuery blurQuery) throws BlurException, TException {
boolean useCacheIfPresent = blurQuery.isUseCacheIfPresent();
boolean cacheResult = blurQuery.isCacheResult();
BlurQuery copy = new BlurQuery(blurQuery);
// These are the fields that play no part in the actual results returned.
// So we are going to normalize the values so that we can reuse the cache
// results.
copy.useCacheIfPresent = false;
copy.maxQueryTime = 0;
copy.uuid = null;
copy.userContext = null;
copy.cacheResult = false;
copy.startTime = 0;
ThriftCacheKey<BlurQuery> key = _thriftCache.getKey(table, getShards(table), copy, BlurQuery.class);
Lock lock = getOrCreateLock(key);
try {
lock.lock();
if (useCacheIfPresent) {
BlurResults results = _thriftCache.get(key, BlurResults.class);
if (results != null) {
return results;
}
}
BlurResults blurResults = super.query(table, blurQuery);
if (cacheResult) {
return _thriftCache.put(key, blurResults);
}
return blurResults;
} finally {
lock.unlock();
}
}
@Override
public FetchResult fetchRow(String table, Selector selector) throws BlurException, TException {
Selector copy = new Selector(selector);
ThriftCacheKey<Selector> key = _thriftCache.getKey(table, getShards(table), copy, Selector.class);
Lock lock = getOrCreateLock(key);
try {
lock.lock();
FetchResult results = _thriftCache.get(key, FetchResult.class);
if (results != null) {
return results;
}
return _thriftCache.put(key, super.fetchRow(table, selector));
} finally {
lock.unlock();
}
}
@Override
public List<FetchResult> fetchRowBatch(String table, List<Selector> selectors) throws BlurException, TException {
Map<Integer, FetchResult> resultMap = new TreeMap<Integer, FetchResult>();
// Maps cache miss request list index to original request list.
Map<Integer, Integer> requestMapping = new HashMap<Integer, Integer>();
List<Selector> selectorRequest = new ArrayList<Selector>();
// Gather hits that are already in cache.
for (int i = 0; i < selectors.size(); i++) {
Selector selector = selectors.get(i);
Selector copy = new Selector(selector);
ThriftCacheKey<Selector> key = _thriftCache.getKey(table, getShards(table), copy, Selector.class);
FetchResult fetchResult = _thriftCache.get(key, FetchResult.class);
if (fetchResult != null) {
resultMap.put(i, fetchResult);
} else {
int index = selectorRequest.size();
requestMapping.put(index, i);
selectorRequest.add(selector);
}
}
if (selectorRequest.size() != 0) {
List<FetchResult> missingResults = super.fetchRowBatch(table, selectorRequest);
for (int i = 0; i < missingResults.size(); i++) {
Selector selector = selectorRequest.get(i);
FetchResult fetchResult = missingResults.get(i);
ThriftCacheKey<Selector> key = _thriftCache.getKey(table, getShards(table), new Selector(selector),
Selector.class);
_thriftCache.put(key, fetchResult);
int originalIndex = requestMapping.get(i);
resultMap.put(originalIndex, fetchResult);
}
}
return toList(resultMap);
}
private List<FetchResult> toList(Map<Integer, FetchResult> resultMap) {
return new ArrayList<FetchResult>(resultMap.values());
}
private Lock getOrCreateLock(ThriftCacheKey<?> key) {
Lock lock = _lockMap.get(key);
if (lock == null) {
Lock nLock = new ReentrantReadWriteLock().writeLock();
Lock pLock = _lockMap.putIfAbsent(key, new InternalLock(nLock, _lockMap, key));
if (pLock == null) {
return nLock;
} else {
return pLock;
}
} else {
return lock;
}
}
private static class InternalLock implements Lock {
private final Lock _lock;
private final ConcurrentMap<ThriftCacheKey<?>, Lock> _lockMap;
private final ThriftCacheKey<?> _key;
public InternalLock(Lock lock, ConcurrentMap<ThriftCacheKey<?>, Lock> lockMap, ThriftCacheKey<?> key) {
_lock = lock;
_lockMap = lockMap;
_key = key;
}
public void lock() {
_lock.lock();
}
public void lockInterruptibly() throws InterruptedException {
_lock.lockInterruptibly();
}
public boolean tryLock() {
return _lock.tryLock();
}
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return _lock.tryLock(time, unit);
}
public void unlock() {
_lock.unlock();
_lockMap.remove(_key);
}
public Condition newCondition() {
return _lock.newCondition();
}
}
private int[] getShards(String table) throws BlurException {
try {
Set<String> keySet = _indexServer.getIndexes(table).keySet();
int[] shards = new int[keySet.size()];
int i = 0;
for (String s : keySet) {
int shardIndex = ShardUtil.getShardIndex(s);
shards[i++] = shardIndex;
}
Arrays.sort(shards);
return shards;
} catch (IOException e) {
throw new BException("Unknown error while trying to get current shards for table [{0}]", e, table);
}
}
}