/*
* Copyright 2006-2012 Amazon Technologies, Inc. or its affiliates.
* Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
* of Amazon Technologies, Inc. or its affiliates. All rights reserved.
*
* Licensed 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 com.amazon.carbonado.qe;
import java.util.LinkedHashMap;
import java.util.Map;
import org.cojen.util.WeakIdentityMap;
import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.util.SoftValuedCache;
/**
* QueryExecutors should be cached since expensive analysis is often required to build
* them. By default, a minimum of 100 query executors can be cached per Storable type.
* The minimum can be changed with the
* "com.amazon.carbonado.qe.QueryExecutorCache.minCapacity" system property.
*
* @author Brian S O'Neill
*/
public class QueryExecutorCache<S extends Storable> implements QueryExecutorFactory<S> {
final static int cMinCapacity;
static {
int minCapacity = 100;
String prop = System.getProperty(QueryExecutorCache.class.getName().concat(".minCapacity"));
if (prop != null) {
try {
minCapacity = Integer.parseInt(prop);
} catch (NumberFormatException e) {
}
}
cMinCapacity = minCapacity;
}
private final QueryExecutorFactory<S> mFactory;
private final Map<Key<S>, QueryExecutor<S>> mPrimaryCache;
// Maps filters to maps which map ordering lists (possibly with hints) to executors.
private final Map<Filter<S>, SoftValuedCache<Object, QueryExecutor<S>>> mFilterToExecutor;
public QueryExecutorCache(QueryExecutorFactory<S> factory) {
if (factory == null) {
throw new IllegalArgumentException();
}
mFactory = factory;
mPrimaryCache = new LinkedHashMap<Key<S>, QueryExecutor<S>>(17, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<Key<S>, QueryExecutor<S>> eldest) {
return size() > cMinCapacity;
}
};
mFilterToExecutor = new WeakIdentityMap(7);
}
public Class<S> getStorableType() {
return mFactory.getStorableType();
}
/**
* Returns an executor from the cache.
*
* @param filter optional filter
* @param ordering optional order-by properties
* @param hints optional query hints
*/
public QueryExecutor<S> executor(Filter<S> filter, OrderingList<S> ordering, QueryHints hints)
throws RepositoryException
{
final Key<S> key = new Key<S>(filter, ordering, hints);
synchronized (mPrimaryCache) {
QueryExecutor<S> executor = mPrimaryCache.get(key);
if (executor != null) {
return executor;
}
}
// Fallback to second level cache, which may still have the executor because
// garbage collection has not reclaimed it yet. It also allows some concurrent
// executor creation, by using filter-specific locks.
SoftValuedCache<Object, QueryExecutor<S>> cache;
synchronized (mFilterToExecutor) {
cache = mFilterToExecutor.get(filter);
if (cache == null) {
cache = SoftValuedCache.newCache(7);
mFilterToExecutor.put(filter, cache);
}
}
Object subKey;
if (hints == null || hints.isEmpty()) {
subKey = ordering;
} else {
// Don't construct key with filter. It is not needed here and it would prevent
// garbage collection of filters.
subKey = new Key(null, ordering, hints);
}
QueryExecutor<S> executor;
synchronized (cache) {
executor = cache.get(subKey);
if (executor == null) {
executor = mFactory.executor(filter, ordering, hints);
cache.put(subKey, executor);
}
}
synchronized (mPrimaryCache) {
mPrimaryCache.put(key, executor);
}
return executor;
}
private static class Key<S extends Storable> {
private final Filter<S> mFilter;
private final OrderingList<S> mOrdering;
private final QueryHints mHints;
Key(Filter<S> filter, OrderingList<S> ordering, QueryHints hints) {
mFilter = filter;
mOrdering = ordering;
mHints = hints;
}
@Override
public int hashCode() {
Filter<S> filter = mFilter;
int hash = filter == null ? 0 : filter.hashCode();
OrderingList<S> ordering = mOrdering;
if (ordering != null) {
hash = hash * 31 + ordering.hashCode();
}
QueryHints hints = mHints;
if (hints != null) {
hash = hash * 31 + hints.hashCode();
}
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Key) {
Key other = (Key) obj;
return equals(mFilter, other.mFilter)
&& equals(mOrdering, other.mOrdering)
&& equals(mHints, other.mHints);
}
return false;
}
private static boolean equals(Object a, Object b) {
return a == null ? b == null : a.equals(b);
}
}
}