blob: 2bb7f459eb13aadb8a416a81918f38e0a9b0aac3 [file] [log] [blame]
/*
* 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.openjpa.jdbc.kernel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.openjpa.jdbc.meta.ClassMapping;
import org.apache.openjpa.jdbc.sql.Result;
import org.apache.openjpa.jdbc.sql.SelectExecutor;
import org.apache.openjpa.kernel.FetchConfiguration;
import org.apache.openjpa.kernel.FinderCache;
import org.apache.openjpa.kernel.FinderQuery;
import org.apache.openjpa.kernel.QueryHints;
import org.apache.openjpa.kernel.QueryStatistics;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.lib.util.StringUtil;
/**
* Implementation of FinderCache for JDBC.
*
* @author Pinaki Poddar
*
* @since 2.0.0
*
*/
public class FinderCacheImpl
implements FinderCache<ClassMapping, SelectExecutor, Result> {
private static final String PATTERN_SEPARATOR = "\\;";
private static final String EXLUDED_BY_USER = "Excluded by user";
private final Map<ClassMapping, FinderQuery<ClassMapping, SelectExecutor, Result>> _delegate;
// Key: class name Value: Reason why excluded
private final Map<String, String> _uncachables;
private List<String> _exclusionPatterns;
private QueryStatistics<ClassMapping> _stats;
private ReentrantLock _lock = new ReentrantLock();
private boolean _enableStats = false;
public FinderCacheImpl() {
_delegate = new HashMap<>();
_uncachables = new HashMap<>();
_stats = new QueryStatistics.None<>();
}
/**
* Get a map-oriented view of the cache.
*
* @return a map of the query string with class names as key.
*/
@Override
public Map<String, String> getMapView() {
lock();
try {
Map<String, String> view = new TreeMap<>();
for (ClassMapping mapping : _delegate.keySet()) {
view.put(mapping.getDescribedType().getName(),
_delegate.get(mapping).getQueryString());
}
return view;
} finally {
unlock();
}
}
/**
* Gets basic statistics of execution and hit count of finder queries.
*/
@Override
public QueryStatistics<ClassMapping> getStatistics() {
return _stats;
}
/**
* Gets the finder query for the given mapping. The get operation can be
* controlled by FetchConfiguration hints.
* {@link QueryHints#HINT_IGNORE_FINDER HINT_IGNORE_FINDER} will ignore
* any cached finder that may exist in this cache and will return null.
* {@link QueryHints#HINT_INVALIDATE_FINDER HINT_INVALIDATE_FINDER} will
* invalidate any cached finder that may exist in this cache and will return
* null.
*
*/
@Override
public FinderQuery<ClassMapping,SelectExecutor,Result>
get(ClassMapping mapping, FetchConfiguration fetch) {
if (fetch.getReadLockLevel() != 0) {
return null;
}
if (!fetch.isFetchConfigurationSQLCacheAdmissible()) {
return null;
}
boolean ignore = isHinted(fetch, QueryHints.HINT_IGNORE_FINDER);
boolean invalidate = isHinted(fetch, QueryHints.HINT_INVALIDATE_FINDER);
if (invalidate) {
invalidate(mapping);
}
if (ignore) {
return null;
}
FinderQuery<ClassMapping, SelectExecutor, Result> result = _delegate.get(mapping);
_stats.recordExecution(mapping);
return result;
}
/**
* Cache a Finder Query for the given mapping and select. The put operation
* can be controlled by FetchConfiguration hints.
* If no entry exists for the given mapping then an attempt is made to
* create a new FinderQuery. The attempt, however, may not be successful
* because all Selects can not be cached.
* @see FinderQueryImpl#newFinder(ClassMapping, Select).
*
* If a entry for the given mapping exists then the value of
* {@link QueryHints#HINT_RECACHE_FINDER HINT_RECACHE_FINDER} hint
* determines whether the existing entry is returned or a new FinderQuery
* with the given argument overwrites the existing one.
*
* @param mapping the class for which the finder is to be cached
* @param select the finder query
* @param fetch may contain hints to control cache operation
*/
@Override
public FinderQuery<ClassMapping, SelectExecutor, Result> cache
(ClassMapping mapping, SelectExecutor select, FetchConfiguration fetch) {
lock();
try {
if (fetch.getReadLockLevel() != 0) {
return null;
}
if (!fetch.isFetchConfigurationSQLCacheAdmissible()) {
return null;
}
boolean recache = isHinted(fetch, QueryHints.HINT_RECACHE_FINDER);
if (isExcluded(mapping)) {
return recache ? put(mapping, select) : null;
}
if (_delegate.containsKey(mapping)) {
return recache ? put(mapping, select) : _delegate.get(mapping);
}
return put(mapping, select);
} finally {
unlock();
}
}
/**
* Creates and puts a FinderQuery in the internal map indexed by the
* given ClassMapping.
* If a new FinderQuery can not be created for the given Select (because
* some Select are not cached), then the mapping is marked invalid.
*
*/
private FinderQuery<ClassMapping, SelectExecutor, Result> put(ClassMapping mapping, SelectExecutor select) {
FinderQuery<ClassMapping, SelectExecutor, Result> finder = FinderQueryImpl.newFinder(mapping, select);
if (finder != null) {
_delegate.put(mapping, finder);
} else {
invalidate(mapping);
}
return finder;
}
/**
* Affirms if the given mapping is excluded from being cached.
*/
@Override
public boolean isExcluded(ClassMapping mapping) {
return mapping != null && isExcluded(mapping.getDescribedType().getName());
}
/**
* Searches the exclusion patterns to find out if the given string matches
* any element.
*/
private boolean isExcluded(String target) {
if (_exclusionPatterns != null && _exclusionPatterns.contains(target))
return true;
return getMatchedExclusionPattern(target) != null;
}
/**
* Adds a pattern for exclusion. Any cached finder whose class name
* matches the given pattern will be marked invalidated as a side-effect.
*/
@Override
public void addExclusionPattern(String pattern) {
lock();
try {
if (_exclusionPatterns == null)
_exclusionPatterns = new ArrayList<>();
_exclusionPatterns.add(pattern);
Collection<ClassMapping> invalidMappings = getMatchedKeys(pattern,
_delegate.keySet());
for (ClassMapping invalidMapping : invalidMappings)
markUncachable(invalidMapping, pattern);
} finally {
unlock();
}
}
/**
* Removes a pattern for exclusion. Any query identifier marked as not
* cachable due to the given pattern will now be removed from the list of
* uncachables as a side-effect.
*/
@Override
public void removeExclusionPattern(String pattern) {
lock();
try {
if (_exclusionPatterns == null)
return;
_exclusionPatterns.remove(pattern);
Collection<String> reborns = getMatchedKeys(pattern,
_uncachables.keySet());
for (String rebornKey : reborns)
_uncachables.remove(rebornKey);
} finally {
unlock();
}
}
/**
* Gets the pattern that matches the given identifier.
*/
private String getMatchedExclusionPattern(String id) {
if (_exclusionPatterns == null || _exclusionPatterns.isEmpty())
return null;
for (String pattern : _exclusionPatterns)
if (matches(pattern, id))
return pattern;
return null;
}
/**
* Gets the elements of the given set that match the given pattern.
*/
private Collection<ClassMapping> getMatchedKeys(String pattern, Set<ClassMapping> set) {
List<ClassMapping> result = new ArrayList<>();
for (ClassMapping entry : set) {
if (matches(pattern, entry)) {
result.add(entry);
}
}
return result;
}
/**
* Gets the elements of the given list which match the given pattern.
*/
private Collection<String> getMatchedKeys(String pattern, Collection<String> coll) {
List<String> result = new ArrayList<>();
for (String key : coll) {
if (matches(pattern, key)) {
result.add(key);
}
}
return result;
}
boolean matches(String pattern, ClassMapping mapping) {
return matches(pattern, mapping.getDescribedType().getName());
}
boolean matches(String pattern, String target) {
return target != null && (target.equals(pattern)
|| target.matches(pattern));
}
@Override
public boolean invalidate(ClassMapping mapping) {
lock();
try {
return _delegate.remove(mapping) != null;
} finally {
unlock();
}
}
@Override
public FinderQuery<ClassMapping, SelectExecutor, Result> markUncachable(ClassMapping mapping) {
return markUncachable(mapping.getDescribedType().getName());
}
public FinderQuery<ClassMapping, SelectExecutor, Result> markUncachable(String id) {
return markUncachable(id, EXLUDED_BY_USER);
}
private FinderQuery<ClassMapping, SelectExecutor, Result> markUncachable(String cls, String reason) {
lock();
try {
boolean excludedByUser = _uncachables.get(cls) == EXLUDED_BY_USER;
if (!excludedByUser)
_uncachables.put(cls, reason);
return _delegate.remove(searchMappingByName(cls));
} finally {
unlock();
}
}
private FinderQuery<ClassMapping, SelectExecutor, Result> markUncachable(ClassMapping mapping, String reason) {
lock();
try {
String cls = mapping.getDescribedType().getName();
boolean excludedByUser = _uncachables.get(cls) == EXLUDED_BY_USER;
if (!excludedByUser)
_uncachables.put(cls, reason);
return _delegate.remove(mapping);
} finally {
unlock();
}
}
ClassMapping searchMappingByName(String cls) {
for (ClassMapping mapping : _delegate.keySet())
if (matches(cls, mapping))
return mapping;
return null;
}
public void setExcludes(String excludes) {
lock();
try {
if (StringUtil.isEmpty(excludes))
return;
if (_exclusionPatterns == null)
_exclusionPatterns = new ArrayList<>();
String[] patterns = excludes.split(PATTERN_SEPARATOR);
for (String pattern : patterns)
addExclusionPattern(pattern);
} finally {
unlock();
}
}
@Override
@SuppressWarnings("unchecked")
public List<String> getExcludes() {
return (List<String>)_exclusionPatterns == null
? Collections.EMPTY_LIST
: Collections.unmodifiableList(_exclusionPatterns);
}
boolean isHinted(FetchConfiguration fetch, String hint) {
if (fetch == null)
return false;
Object result = fetch.getHint(hint);
return result != null && "true".equalsIgnoreCase(result.toString());
}
void lock() {
if (_lock != null)
_lock.lock();
}
void unlock() {
if (_lock != null && _lock.isLocked())
_lock.unlock();
}
public void setEnableStats(boolean b) {
_enableStats = b;
if (_enableStats) {
_stats = new QueryStatistics.Default<>();
}
}
public boolean getEnableStats() {
return _enableStats;
}
// ----------------------------------------------------
// Configuration contract
// ----------------------------------------------------
@Override
public void startConfiguration() {
}
@Override
public void setConfiguration(Configuration conf) {
}
@Override
public void endConfiguration() {
}
}