/**********************************************************************
Copyright (c) 2007 Erik Bengtson and others. 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.
Contributors:
2008 Andy Jefferson - check on datastore when getting query support
2008 Andy Jefferson - query cache
...
***********************************************************************/
package org.datanucleus.store.query;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.datanucleus.ClassConstants;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.ExecutionContext;
import org.datanucleus.NucleusContext;
import org.datanucleus.Configuration;
import org.datanucleus.PropertyNames;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.plugin.ConfigurationElement;
import org.datanucleus.plugin.PluginManager;
import org.datanucleus.query.QueryUtils;
import org.datanucleus.query.compiler.QueryCompilation;
import org.datanucleus.query.compiler.QueryCompilationCache;
import org.datanucleus.query.inmemory.InvocationEvaluator;
import org.datanucleus.query.inmemory.method.ArrayContainsMethod;
import org.datanucleus.query.inmemory.method.ArraySizeMethod;
import org.datanucleus.store.StoreManager;
import org.datanucleus.store.query.cache.QueryDatastoreCompilationCache;
import org.datanucleus.store.query.cache.QueryResultsCache;
import org.datanucleus.util.Localiser;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.StringUtils;
/**
* Manages the creation, compilation and results of queries.
* Provides caching of query compilations (generic and datastore-specific) and results.
*/
public class QueryManagerImpl implements QueryManager
{
protected NucleusContext nucleusCtx;
protected StoreManager storeMgr;
/** Cache for generic query compilations. */
protected QueryCompilationCache queryCompilationCache = null;
/** Cache for datastore query compilations. */
protected QueryDatastoreCompilationCache queryCompilationCacheDatastore = null;
/** Cache for query results. */
protected QueryResultsCache queryResultsCache = null;
/** Cache of InvocationEvaluator objects keyed by the method name, for use by in-memory querying. */
protected Map<String, Map<Object, InvocationEvaluator>> inmemoryQueryMethodEvaluatorMap = new ConcurrentHashMap<String, Map<Object,InvocationEvaluator>>();
public QueryManagerImpl(NucleusContext nucleusContext, StoreManager storeMgr)
{
this.nucleusCtx = nucleusContext;
this.storeMgr = storeMgr;
// Instantiate the query compilation cache (generic)
Configuration conf = nucleusCtx.getConfiguration();
String cacheType = conf.getStringProperty(PropertyNames.PROPERTY_CACHE_QUERYCOMPILE_TYPE);
if (cacheType != null && !cacheType.equalsIgnoreCase("none"))
{
String cacheClassName = nucleusCtx.getPluginManager().getAttributeValueForExtension("org.datanucleus.cache_query_compilation", "name", cacheType, "class-name");
if (cacheClassName == null)
{
// Plugin of this name not found
throw new NucleusUserException(Localiser.msg("021500", cacheType)).setFatal();
}
try
{
// Create an instance of the Query Cache
queryCompilationCache = (QueryCompilationCache)nucleusCtx.getPluginManager().createExecutableExtension("org.datanucleus.cache_query_compilation",
"name", cacheType, "class-name", new Class[] {ClassConstants.NUCLEUS_CONTEXT}, new Object[] {nucleusCtx});
if (NucleusLogger.CACHE.isDebugEnabled())
{
NucleusLogger.CACHE.debug(Localiser.msg("021502", cacheClassName));
}
}
catch (Exception e)
{
// Class name for this Query cache plugin is not found!
throw new NucleusUserException(Localiser.msg("021501", cacheType, cacheClassName), e).setFatal();
}
}
// Instantiate the query compilation cache (datastore)
cacheType = conf.getStringProperty(PropertyNames.PROPERTY_CACHE_QUERYCOMPILEDATASTORE_TYPE);
if (cacheType != null && !cacheType.equalsIgnoreCase("none"))
{
String cacheClassName = nucleusCtx.getPluginManager().getAttributeValueForExtension("org.datanucleus.cache_query_compilation_store", "name", cacheType, "class-name");
if (cacheClassName == null)
{
// Plugin of this name not found
throw new NucleusUserException(Localiser.msg("021500", cacheType)).setFatal();
}
try
{
// Create an instance of the Query Cache
queryCompilationCacheDatastore = (QueryDatastoreCompilationCache)nucleusCtx.getPluginManager().createExecutableExtension("org.datanucleus.cache_query_compilation_store",
"name", cacheType, "class-name", new Class[] {ClassConstants.NUCLEUS_CONTEXT}, new Object[] {nucleusCtx});
if (NucleusLogger.CACHE.isDebugEnabled())
{
NucleusLogger.CACHE.debug(Localiser.msg("021502", cacheClassName));
}
}
catch (Exception e)
{
// Class name for this Query cache plugin is not found!
throw new NucleusUserException(Localiser.msg("021501", cacheType, cacheClassName), e).setFatal();
}
}
// Instantiate the query results cache
cacheType = conf.getStringProperty(PropertyNames.PROPERTY_CACHE_QUERYRESULTS_TYPE);
if (cacheType != null && !cacheType.equalsIgnoreCase("none"))
{
String cacheClassName = nucleusCtx.getPluginManager().getAttributeValueForExtension("org.datanucleus.cache_query_result", "name", cacheType, "class-name");
if (cacheClassName == null)
{
// Plugin of this name not found
throw new NucleusUserException(Localiser.msg("021500", cacheType)).setFatal();
}
try
{
// Create an instance of the Query Cache
queryResultsCache = (QueryResultsCache)nucleusCtx.getPluginManager().createExecutableExtension("org.datanucleus.cache_query_result",
"name", cacheType, "class-name", new Class[] {ClassConstants.NUCLEUS_CONTEXT}, new Object[] {nucleusCtx});
if (NucleusLogger.CACHE.isDebugEnabled())
{
NucleusLogger.CACHE.debug(Localiser.msg("021502", cacheClassName));
}
}
catch (Exception e)
{
// Class name for this Query cache plugin is not found!
throw new NucleusUserException(Localiser.msg("021501", cacheType, cacheClassName), e).setFatal();
}
}
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#close()
*/
@Override
public void close()
{
if (queryCompilationCache != null)
{
queryCompilationCache.close();
queryCompilationCache = null;
}
if (queryCompilationCacheDatastore != null)
{
queryCompilationCacheDatastore.close();
queryCompilationCacheDatastore = null;
}
if (queryResultsCache != null)
{
queryResultsCache.close();
queryResultsCache = null;
}
inmemoryQueryMethodEvaluatorMap.clear();
inmemoryQueryMethodEvaluatorMap = null;
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#newQuery(java.lang.String, org.datanucleus.ExecutionContext, java.lang.Object)
*/
@Override
public Query newQuery(String language, ExecutionContext ec, Object query)
{
if (language == null)
{
return null;
}
String languageImpl = language;
// Find the query support for this language and this datastore
try
{
if (query == null)
{
Class[] argsClass = new Class[] {ClassConstants.STORE_MANAGER, ClassConstants.EXECUTION_CONTEXT};
Object[] args = new Object[] {storeMgr, ec};
Query q = (Query) ec.getNucleusContext().getPluginManager().createExecutableExtension("org.datanucleus.store_query_query",
new String[] {"name", "datastore"}, new String[] {languageImpl, ec.getStoreManager().getStoreManagerKey()}, "class-name", argsClass, args);
if (q == null)
{
// No query support for this language
throw new NucleusException(Localiser.msg("021034", languageImpl, ec.getStoreManager().getStoreManagerKey()));
}
return q;
}
Query q = null;
if (query instanceof String)
{
// Try XXXQuery(ExecutionContext, String);
Class[] argsClass = new Class[]{ClassConstants.STORE_MANAGER, ClassConstants.EXECUTION_CONTEXT, String.class};
Object[] args = new Object[]{storeMgr, ec, query};
q = (Query) ec.getNucleusContext().getPluginManager().createExecutableExtension("org.datanucleus.store_query_query",
new String[] {"name", "datastore"}, new String[] {languageImpl, ec.getStoreManager().getStoreManagerKey()}, "class-name", argsClass, args);
if (q == null)
{
// No query support for this language
throw new NucleusException(Localiser.msg("021034", languageImpl, ec.getStoreManager().getStoreManagerKey()));
}
}
else if (query instanceof Query)
{
// Try XXXQuery(StoreManager, ExecutionContext, Query.class);
Class[] argsClass = new Class[]{ClassConstants.STORE_MANAGER, ClassConstants.EXECUTION_CONTEXT, query.getClass()};
Object[] args = new Object[]{storeMgr, ec, query};
q = (Query) ec.getNucleusContext().getPluginManager().createExecutableExtension("org.datanucleus.store_query_query",
new String[] {"name", "datastore"}, new String[] {languageImpl, ec.getStoreManager().getStoreManagerKey()}, "class-name", argsClass, args);
if (q == null)
{
// No query support for this language
throw new NucleusException(Localiser.msg("021034", languageImpl, ec.getStoreManager().getStoreManagerKey()));
}
}
else
{
// Try XXXQuery(StoreManager, ExecutionContext, Object);
Class[] argsClass = new Class[]{ClassConstants.STORE_MANAGER, ClassConstants.EXECUTION_CONTEXT, Object.class};
Object[] args = new Object[]{storeMgr, ec, query};
q = (Query) ec.getNucleusContext().getPluginManager().createExecutableExtension("org.datanucleus.store_query_query",
new String[] {"name", "datastore"}, new String[] {languageImpl, ec.getStoreManager().getStoreManagerKey()}, "class-name", argsClass, args);
if (q == null)
{
// No query support for this language
throw new NucleusException(Localiser.msg("021034", languageImpl, ec.getStoreManager().getStoreManagerKey()));
}
}
return q;
}
catch (InvocationTargetException e)
{
Throwable t = e.getTargetException();
if (t instanceof RuntimeException)
{
throw (RuntimeException) t;
}
else if (t instanceof Error)
{
throw (Error) t;
}
else
{
throw new NucleusException(t.getMessage(), t).setFatal();
}
}
catch (Exception e)
{
throw new NucleusException(e.getMessage(), e).setFatal();
}
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#getQueryCompilationCache()
*/
@Override
public synchronized QueryCompilationCache getQueryCompilationCache()
{
return queryCompilationCache;
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#addQueryCompilation(java.lang.String, java.lang.String, org.datanucleus.query.compiler.QueryCompilation)
*/
@Override
public synchronized void addQueryCompilation(String language, String query, QueryCompilation compilation)
{
if (queryCompilationCache != null)
{
queryCompilationCache.put(language + ":" + query, compilation);
}
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#deleteQueryCompilation(java.lang.String, java.lang.String)
*/
@Override
public void removeQueryCompilation(String language, String query)
{
if (queryCompilationCache != null)
{
queryCompilationCache.evict(language + ":" + query);
}
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#getQueryCompilationForQuery(java.lang.String, java.lang.String)
*/
@Override
public synchronized QueryCompilation getQueryCompilationForQuery(String language, String query)
{
if (queryCompilationCache != null)
{
String queryKey = language + ":" + query;
QueryCompilation compilation = queryCompilationCache.get(queryKey);
if (compilation != null)
{
if (NucleusLogger.QUERY.isDebugEnabled())
{
NucleusLogger.QUERY.debug(Localiser.msg("021079", query, language));
}
return compilation;
}
}
return null;
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#getQueryDatastoreCompilationCache()
*/
@Override
public synchronized QueryDatastoreCompilationCache getQueryDatastoreCompilationCache()
{
return queryCompilationCacheDatastore;
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#addDatastoreQueryCompilation(java.lang.String, java.lang.String, java.lang.String, java.lang.Object)
*/
@Override
public synchronized void addDatastoreQueryCompilation(String datastore, String language, String query,
Object compilation)
{
if (queryCompilationCacheDatastore != null)
{
String queryKey = language + ":" + query;
queryCompilationCacheDatastore.put(queryKey, compilation);
}
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#deleteDatastoreQueryCompilation(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public synchronized void removeDatastoreQueryCompilation(String datastore, String language, String query)
{
if (queryCompilationCacheDatastore != null)
{
String queryKey = language + ":" + query;
queryCompilationCacheDatastore.evict(queryKey);
}
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#getDatastoreQueryCompilation(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public synchronized Object getDatastoreQueryCompilation(String datastore, String language, String query)
{
if (queryCompilationCacheDatastore != null)
{
String queryKey = language + ":" + query;
Object compilation = queryCompilationCacheDatastore.get(queryKey);
if (compilation != null)
{
if (NucleusLogger.QUERY.isDebugEnabled())
{
NucleusLogger.QUERY.debug(Localiser.msg("021080", query, language, datastore));
}
return compilation;
}
}
return null;
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#getQueryResultsCache()
*/
@Override
public synchronized QueryResultsCache getQueryResultsCache()
{
return queryResultsCache;
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#evictQueryResultsForType(java.lang.Class)
*/
@Override
public synchronized void evictQueryResultsForType(Class cls)
{
if (queryResultsCache != null)
{
queryResultsCache.evict(cls);
}
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#addDatastoreQueryResult(org.datanucleus.store.query.Query, java.util.Map, java.util.List)
*/
@Override
public synchronized void addQueryResult(Query query, Map params, List<Object> results)
{
if (queryResultsCache != null)
{
String queryKey = QueryUtils.getKeyForQueryResultsCache(query, params);
queryResultsCache.put(queryKey, results);
if (NucleusLogger.QUERY.isDebugEnabled())
{
NucleusLogger.QUERY.debug(Localiser.msg("021081", query, results.size()));
}
}
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#getDatastoreQueryResult(org.datanucleus.store.query.Query, java.util.Map)
*/
@Override
public synchronized List<Object> getQueryResult(Query query, Map params)
{
if (queryResultsCache != null)
{
String queryKey = QueryUtils.getKeyForQueryResultsCache(query, params);
List<Object> results = queryResultsCache.get(queryKey);
if (results != null)
{
if (NucleusLogger.QUERY.isDebugEnabled())
{
NucleusLogger.QUERY.debug(Localiser.msg("021082", query, results.size()));
}
}
return results;
}
return null;
}
/* (non-Javadoc)
* @see org.datanucleus.store.query.QueryManager#getInMemoryEvaluatorForMethod(java.lang.Class, java.lang.String)
*/
@Override
public InvocationEvaluator getInMemoryEvaluatorForMethod(Class type, String methodName)
{
// Hardcode support for Array.size()/Array.length()/Array.contains() since not currently pluggable
if (type != null && type.isArray())
{
// TODO Cache these
if (methodName.equals("size") || methodName.equals("length"))
{
return new ArraySizeMethod();
}
else if (methodName.equals("contains"))
{
return new ArrayContainsMethod();
}
}
Map<Object, InvocationEvaluator> evaluatorsForMethod = inmemoryQueryMethodEvaluatorMap.get(methodName);
if (evaluatorsForMethod != null)
{
Iterator evaluatorClsIter = evaluatorsForMethod.entrySet().iterator();
while (evaluatorClsIter.hasNext())
{
Map.Entry<Object, InvocationEvaluator> entry = (Entry<Object, InvocationEvaluator>) evaluatorClsIter.next();
Object clsKey = entry.getKey();
if (clsKey instanceof Class && ((Class)clsKey).isAssignableFrom(type))
{
return entry.getValue();
}
else if (clsKey instanceof String && ((String)clsKey).equals("STATIC") && type == null)
{
// Can only be one static method so just return it
return entry.getValue();
}
}
return null;
}
// Not yet loaded anything for this method
ClassLoaderResolver clr = nucleusCtx.getClassLoaderResolver(type != null ? type.getClassLoader() : null);
PluginManager pluginMgr = nucleusCtx.getPluginManager();
ConfigurationElement[] elems = pluginMgr.getConfigurationElementsForExtension("org.datanucleus.query_method_evaluators", "method", methodName);
Map<Object, InvocationEvaluator> evaluators = new HashMap();
InvocationEvaluator requiredEvaluator = null;
if (elems == null)
{
return null;
}
for (int i=0;i<elems.length;i++)
{
try
{
String evalName = elems[i].getAttribute("evaluator");
InvocationEvaluator eval = (InvocationEvaluator)pluginMgr.createExecutableExtension("org.datanucleus.query_method_evaluators",
new String[] {"method", "evaluator"}, new String[] {methodName, evalName}, "evaluator", null, null);
String elemClsName = elems[i].getAttribute("class");
if (elemClsName != null && StringUtils.isWhitespace(elemClsName))
{
elemClsName = null;
}
if (elemClsName == null)
{
// Static method call
if (type == null)
{
// Evaluator is applicable to the required type
requiredEvaluator = eval;
}
evaluators.put("STATIC", eval);
}
else
{
Class elemCls = clr.classForName(elemClsName);
if (elemCls.isAssignableFrom(type))
{
// Evaluator is applicable to the required type
requiredEvaluator = eval;
}
evaluators.put(elemCls, eval);
}
}
catch (Exception e)
{
// Impossible to create the evaluator (class doesn't exist?) TODO Log this?
}
}
// Store evaluators for this method name
inmemoryQueryMethodEvaluatorMap.put(methodName, evaluators);
return requiredEvaluator;
}
}