/* * 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 implements QueryFactory { private final Class mType; private final boolean mLazySetExecutor; private final SoftValuedCache> mStringToQuery; // Maps filters to maps which map ordering lists to queries. private final Map, Map, Query>> mFilterToQuery; protected StandardQueryFactory(Class 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 type, boolean lazySetExecutor) { if (type == null) { throw new IllegalArgumentException(); } mType = type; mLazySetExecutor = lazySetExecutor; mStringToQuery = SoftValuedCache.newCache(7); mFilterToQuery = new WeakIdentityMap(7); } public Class getStorableType() { return mType; } /** * Returns a new or cached query that fetches everything. */ public Query 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 query(String filter) throws FetchException { synchronized (mStringToQuery) { Query 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 query(Filter 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 query(Filter filter, OrderingList 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 query(Filter filter, OrderingList ordering, QueryHints hints) throws FetchException { filter = filter.bind(); Map, Query> 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 query; synchronized (map) { query = map.get(ordering); if (query == null) { FilterValues values = filter.initialFilterValues(); if (values == null && filter.isClosed()) { query = new EmptyQuery(this, ordering); } else { StandardQuery 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 query(Filter filter, FilterValues values, OrderingList 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 query(Filter filter, FilterValues values, OrderingList ordering, QueryHints hints) throws FetchException { Query 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 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 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 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 createQuery(Filter filter, FilterValues values, OrderingList ordering, QueryHints hints) throws FetchException; private ArrayList> gatherQueries() { // Copy all queries and operate on the copy instead of holding lock for // potentially a long time. ArrayList> queries = new ArrayList>(); synchronized (mFilterToQuery) { for (Map, Query> map : mFilterToQuery.values()) { for (Query query : map.values()) { if (query instanceof StandardQuery) { queries.add((StandardQuery) query); } } } } return queries; } }