/*
* 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.ArrayList;
import java.util.Map;
import org.cojen.util.SoftValuedHashMap;
import org.cojen.util.WeakIdentityMap;
import com.amazon.carbonado.FetchException;
import com.amazon.carbonado.Query;
import com.amazon.carbonado.RepositoryException;
import com.amazon.carbonado.Storable;
import com.amazon.carbonado.filter.Filter;
import com.amazon.carbonado.filter.FilterValues;
import com.amazon.carbonado.util.SoftValuedCache;
/**
* Builds and caches StandardQuery instances.
*
* @author Brian S O'Neill
*/
public abstract class StandardQueryFactory<S extends Storable> implements QueryFactory<S> {
private final Class<S> mType;
private final boolean mLazySetExecutor;
private final SoftValuedCache<String, Query<S>> mStringToQuery;
// Maps filters to maps which map ordering lists to queries.
private final Map<Filter<S>, Map<OrderingList<S>, Query<S>>> mFilterToQuery;
protected StandardQueryFactory(Class<S> type) {
this(type, false);
}
/**
* @param lazySetExecutor by default, query executors are built and set
* eagerly. Pass true to build and set executor on first query use.
*/
protected StandardQueryFactory(Class<S> type, boolean lazySetExecutor) {
if (type == null) {
throw new IllegalArgumentException();
}
mType = type;
mLazySetExecutor = lazySetExecutor;
mStringToQuery = SoftValuedCache.newCache(7);
mFilterToQuery = new WeakIdentityMap(7);
}
public Class<S> getStorableType() {
return mType;
}
/**
* Returns a new or cached query that fetches everything.
*/
public Query<S> query() throws FetchException {
return query(Filter.getOpenFilter(mType), null);
}
/**
* Returns a new or cached query for the given filter.
*
* @throws IllegalArgumentException if filter is null
*/
public Query<S> query(String filter) throws FetchException {
synchronized (mStringToQuery) {
Query<S> query = mStringToQuery.get(filter);
if (query == null) {
if (filter == null) {
throw new IllegalArgumentException("Query filter must not be null");
}
query = query(Filter.filterFor(mType, filter), null);
mStringToQuery.put(filter, query);
}
return query;
}
}
/**
* Returns a new or cached query for the given filter.
*
* @throws IllegalArgumentException if filter is null
*/
public Query<S> query(Filter<S> filter) throws FetchException {
return query(filter, null);
}
/**
* Returns a new or cached query for the given query specification.
*
* @throws IllegalArgumentException if filter is null
*/
public Query<S> query(Filter<S> filter, OrderingList<S> ordering) throws FetchException {
return query(filter, ordering, null);
}
/**
* Returns a new or cached query for the given query specification.
*
* @throws IllegalArgumentException if filter is null
*/
public Query<S> query(Filter<S> filter, OrderingList<S> ordering, QueryHints hints)
throws FetchException
{
filter = filter.bind();
Map<OrderingList<S>, Query<S>> map;
synchronized (mFilterToQuery) {
map = mFilterToQuery.get(filter);
if (map == null) {
if (filter == null) {
throw new IllegalArgumentException("Query filter must not be null");
}
map = new SoftValuedHashMap(7);
mFilterToQuery.put(filter, map);
}
}
Query<S> query;
synchronized (map) {
query = map.get(ordering);
if (query == null) {
FilterValues<S> values = filter.initialFilterValues();
if (values == null && filter.isClosed()) {
query = new EmptyQuery<S>(this, ordering);
} else {
StandardQuery<S> standardQuery = createQuery(filter, values, ordering, hints);
if (!mLazySetExecutor) {
try {
standardQuery.setExecutor();
} catch (RepositoryException e) {
throw e.toFetchException();
}
}
query = standardQuery;
}
map.put(ordering, query);
}
}
return query;
}
/**
* Returns a new or cached query for the given query specification.
*
* @param filter optional filter object, defaults to open filter if null
* @param values optional values object, defaults to filter initial values
* @param ordering optional order-by properties
*/
public Query<S> query(Filter<S> filter, FilterValues<S> values, OrderingList<S> ordering)
throws FetchException
{
return query(filter, values, ordering, null);
}
/**
* Returns a new or cached query for the given query specification.
*
* @param filter optional filter object, defaults to open filter if null
* @param values optional values object, defaults to filter initial values
* @param ordering optional order-by properties
* @param hints optional hints
*/
public Query<S> query(Filter<S> filter, FilterValues<S> values, OrderingList<S> ordering,
QueryHints hints)
throws FetchException
{
Query<S> query = query(filter != null ? filter : Filter.getOpenFilter(mType),
ordering, hints);
if (values != null) {
query = query.withValues(values.getSuppliedValues());
}
return query;
}
/**
* For each cached query, calls {@link StandardQuery#setExecutor}.
*/
public void setExecutors() throws RepositoryException {
for (StandardQuery<S> query : gatherQueries()) {
query.setExecutor();
}
}
/**
* For each cached query, calls {@link StandardQuery#resetExecutor}.
* This call can be used to rebuild all cached query plans after the set of
* available indexes has changed.
*/
public void resetExecutors() throws RepositoryException {
for (StandardQuery<S> query : gatherQueries()) {
query.resetExecutor();
}
}
/**
* For each cached query, calls {@link StandardQuery#clearExecutor}.
* This call can be used to clear all cached query plans after the set of
* available indexes has changed.
*/
public void clearExecutors() {
for (StandardQuery<S> query : gatherQueries()) {
query.clearExecutor();
}
}
/**
* Implement this method to return query implementations.
*
* @param filter optional filter object, defaults to open filter if null
* @param values optional values object, defaults to filter initial values
* @param ordering optional order-by properties
* @param hints optional hints
*/
protected abstract StandardQuery<S> createQuery(Filter<S> filter,
FilterValues<S> values,
OrderingList<S> ordering,
QueryHints hints)
throws FetchException;
private ArrayList<StandardQuery<S>> gatherQueries() {
// Copy all queries and operate on the copy instead of holding lock for
// potentially a long time.
ArrayList<StandardQuery<S>> queries = new ArrayList<StandardQuery<S>>();
synchronized (mFilterToQuery) {
for (Map<OrderingList<S>, Query<S>> map : mFilterToQuery.values()) {
for (Query<S> query : map.values()) {
if (query instanceof StandardQuery) {
queries.add((StandardQuery<S>) query);
}
}
}
}
return queries;
}
}