OPENJPA-703: Beef up strong/weak exclusion of cache entries. Narrow down reason for exclusion.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@791971 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2009-07-07 20:29:34 +00:00
parent 9f62d5ebfa
commit dcaa13461e
8 changed files with 240 additions and 101 deletions

View File

@ -51,21 +51,21 @@ import org.apache.openjpa.lib.util.Localizer;
*/
public class PreparedQueryCacheImpl implements PreparedQueryCache {
private static final String PATTERN_SEPARATOR = "\\;";
private static final String EXLUDED_BY_USER = "Excluded by user";
// Key: Query identifier
private final Map<String, PreparedQuery> _delegate;
// Key: Query identifier Value: Reason why excluded
private final Map<String, String> _uncachables;
private List<String> _exclusionPatterns;
private final Map<String, Exclusion> _uncachables;
private final List<Exclusion> _exclusionPatterns;
private final QueryStatistics<String> _stats;
private ReentrantLock _lock = new ReentrantLock();
private Log _log;
private Localizer _loc = Localizer.forPackage(PreparedQueryCacheImpl.class);
private static Localizer _loc = Localizer.forPackage(PreparedQueryCacheImpl.class);
public PreparedQueryCacheImpl() {
_delegate = new HashMap<String, PreparedQuery>();
_uncachables = new HashMap<String, String>();
_uncachables = new HashMap<String, Exclusion>();
_stats = new QueryStatistics.Default<String>();
_exclusionPatterns = new ArrayList<Exclusion>();
}
public Boolean register(String id, Query query, FetchConfiguration hints) {
@ -113,9 +113,9 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
_log.warn(_loc.get("prepared-query-not-cachable", id));
return false;
}
String pattern = getMatchedExclusionPattern(id);
if (pattern != null) {
markUncachable(id, pattern);
Exclusion exclusion = getMatchedExclusionPattern(id);
if (exclusion != null) {
markUncachable(id, exclusion);
return false;
}
_delegate.put(id, q);
@ -133,9 +133,9 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
if (pq == null)
return null;
boolean cacheable = pq.initialize(result);
if (!cacheable) {
markUncachable(key);
Exclusion exclusion = pq.initialize(result);
if (exclusion != null) {
markUncachable(key, exclusion);
return null;
}
return pq;
@ -144,8 +144,8 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
public boolean invalidate(String id) {
lock();
try {
if (_log.isTraceEnabled())
_log.trace(_loc.get("prepared-query-invalidate", id));
if (_log != null && _log.isInfoEnabled())
_log.info(_loc.get("prepared-query-invalidate", id));
return _delegate.remove(id) != null;
} finally {
unlock();
@ -174,22 +174,12 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
}
}
public PreparedQuery markUncachable(String id) {
return markUncachable(id, EXLUDED_BY_USER);
}
private PreparedQuery markUncachable(String id, String reason) {
public PreparedQuery markUncachable(String id, Exclusion exclusion) {
lock();
try {
boolean excludedByUser = _uncachables.get(id) == EXLUDED_BY_USER;
if (!excludedByUser)
_uncachables.put(id, reason);
if (_log != null && _log.isInfoEnabled()) {
if (excludedByUser)
_log.info(_loc.get("prepared-query-uncache-strong", id));
else
_log.info(_loc.get("prepared-query-uncache-weak", id,
reason));
if (_uncachables.put(id, exclusion) == null) {
if (_log != null && _log.isInfoEnabled())
_log.info(_loc.get("prepared-query-uncache", id, exclusion));
}
return _delegate.remove(id);
} finally {
@ -197,8 +187,8 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
}
}
public boolean isExcluded(String id) {
return getMatchedExclusionPattern(id) != null;
public Exclusion isExcluded(String id) {
return getMatchedExclusionPattern(id);
}
public void setExcludes(String excludes) {
@ -206,8 +196,6 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
try {
if (StringUtils.isEmpty(excludes))
return;
if (_exclusionPatterns == null)
_exclusionPatterns = new ArrayList<String>();
String[] patterns = excludes.split(PATTERN_SEPARATOR);
for (String pattern : patterns)
addExclusionPattern(pattern);
@ -216,9 +204,8 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
}
}
public List<String> getExcludes() {
return _exclusionPatterns == null ? Collections.EMPTY_LIST :
Collections.unmodifiableList(_exclusionPatterns);
public List<Exclusion> getExcludes() {
return Collections.unmodifiableList(_exclusionPatterns);
}
/**
@ -228,16 +215,14 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
public void addExclusionPattern(String pattern) {
lock();
try {
if (_exclusionPatterns == null)
_exclusionPatterns = new ArrayList<String>();
_exclusionPatterns.add(pattern);
Collection<String> invalidKeys = getMatchedKeys(pattern,
_delegate.keySet());
if (!invalidKeys.isEmpty() && _log != null && _log.isInfoEnabled())
_log.info(_loc.get("prepared-query-add-pattern", pattern,
invalidKeys.size(), invalidKeys));
for (String invalidKey : invalidKeys)
markUncachable(invalidKey, pattern);
String reason = _loc.get("prepared-query-excluded-by-user", pattern).getMessage();
Exclusion exclusion = new WeakExclusion(pattern, reason);
_exclusionPatterns.add(exclusion);
Collection<String> invalidKeys = getMatchedKeys(pattern, _delegate.keySet());
for (String invalidKey : invalidKeys) {
Exclusion invalid = new WeakExclusion(invalidKey, reason);
markUncachable(invalidKey, invalid);
}
} finally {
unlock();
}
@ -251,15 +236,14 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
public void removeExclusionPattern(String pattern) {
lock();
try {
if (_exclusionPatterns == null)
return;
_exclusionPatterns.remove(pattern);
Exclusion exclusion = new WeakExclusion(pattern, null);
_exclusionPatterns.remove(exclusion);
Collection<String> reborns = getMatchedKeys(pattern, _uncachables);
if (!reborns.isEmpty() && _log != null && _log.isInfoEnabled())
_log.info(_loc.get("prepared-query-remove-pattern", pattern,
reborns.size(), reborns));
for (String rebornKey : reborns)
_uncachables.remove(rebornKey);
for (String rebornKey : reborns) {
_uncachables.remove(rebornKey);
if (_log != null && _log.isInfoEnabled())
_log.info(_loc.get("prepared-query-remove-pattern", pattern, rebornKey));
}
} finally {
unlock();
}
@ -272,11 +256,9 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
/**
* 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))
private Exclusion getMatchedExclusionPattern(String id) {
for (Exclusion pattern : _exclusionPatterns)
if (pattern.matches(id))
return pattern;
return null;
}
@ -284,11 +266,11 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
/**
* Gets the keys of the given map whose values match the given pattern.
*/
private Collection<String> getMatchedKeys(String pattern,
Map<String,String> map) {
private Collection<String> getMatchedKeys(String pattern, Map<String,Exclusion> map) {
List<String> result = new ArrayList<String>();
for (Map.Entry<String, String> entry : map.entrySet()) {
if (matches(pattern, entry.getValue())) {
for (Map.Entry<String, Exclusion> entry : map.entrySet()) {
Exclusion exclusion = entry.getValue();
if (!exclusion.isStrong() && exclusion.matches(pattern)) {
result.add(entry.getKey());
}
}
@ -298,8 +280,7 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
/**
* Gets the elements of the given list which match the given pattern.
*/
private Collection<String> getMatchedKeys(String pattern,
Collection<String> coll) {
private Collection<String> getMatchedKeys(String pattern, Collection<String> coll) {
List<String> result = new ArrayList<String>();
for (String key : coll) {
if (matches(pattern, key)) {
@ -348,4 +329,91 @@ public class PreparedQueryCacheImpl implements PreparedQueryCache {
public void endConfiguration() {
}
/**
* An immutable abstract pattern for exclusion.
*
*/
private static abstract class ExclusionPattern implements PreparedQueryCache.Exclusion {
private final boolean _strong;
private final String _pattern;
private final String _reason;
private static Localizer _loc = Localizer.forPackage(PreparedQueryCacheImpl.class);
private static String STRONG = _loc.get("strong-exclusion").getMessage();
private static String WEAK = _loc.get("weak-exclusion").getMessage();
public ExclusionPattern(boolean _strong, String _pattern, String _reason) {
super();
this._strong = _strong;
this._pattern = _pattern;
this._reason = _reason;
}
public String getPattern() {
return _pattern;
}
public String getReason() {
return _reason;
}
public boolean isStrong() {
return _strong;
}
public boolean matches(String id) {
return _pattern != null && (_pattern.equals(id) || _pattern.matches(id));
}
/**
* Equals by strength and pattern (not by reason).
*/
@Override
public final boolean equals(Object other) {
if (other == this)
return true;
if (!(other instanceof Exclusion))
return false;
Exclusion that = (Exclusion)other;
return this._strong == that.isStrong()
&& StringUtils.equals(this._pattern, that.getPattern());
}
@Override
public int hashCode() {
return (_strong ? 1 : 0)
+ (_pattern == null ? 0 : _pattern.hashCode());
}
public String toString() {
StringBuilder buf = new StringBuilder();
buf.append(_strong ? STRONG : WEAK);
if (_reason != null)
buf.append(_reason);
return buf.toString();
}
}
/**
* Strong exclusion.
*
*/
public static class StrongExclusion extends ExclusionPattern {
public StrongExclusion(String pattern, String reason) {
super(true, pattern, reason);
}
}
/**
* Weak exclusion.
*
*/
public static class WeakExclusion extends ExclusionPattern {
public WeakExclusion(String pattern, String reason) {
super(false, pattern, reason);
}
}
}

View File

@ -40,6 +40,7 @@ import org.apache.openjpa.kernel.Query;
import org.apache.openjpa.kernel.QueryImpl;
import org.apache.openjpa.kernel.QueryLanguages;
import org.apache.openjpa.kernel.StoreQuery;
import org.apache.openjpa.kernel.PreparedQueryCache.Exclusion;
import org.apache.openjpa.kernel.exps.QueryExpressions;
import org.apache.openjpa.lib.rop.ResultList;
import org.apache.openjpa.lib.util.Localizer;
@ -153,42 +154,52 @@ public class PreparedQueryImpl implements PreparedQuery {
* The input argument is processed only if it is a {@link ResultList} with
* an attached {@link SelectResultObjectProvider} as its
* {@link ResultList#getUserObject() user object}.
*
* @return an exclusion if can not be initialized for some reason.
* null if initialization is successful.
*/
public boolean initialize(Object result) {
public Exclusion initialize(Object result) {
if (isInitialized())
return true;
SelectExecutor selector = extractSelectExecutor(result);
return null;
Object[] extract = extractSelectExecutor(result);
SelectExecutor selector = (SelectExecutor)extract[0];
if (selector == null)
return new PreparedQueryCacheImpl.StrongExclusion(_id, ((Localizer.Message)extract[1]).getMessage());
if (selector == null || selector.hasMultipleSelects()
|| ((selector instanceof Union)
&& (((Union)selector).getSelects().length != 1)))
return false;
return new PreparedQueryCacheImpl.StrongExclusion(_id, _loc.get("exclude-multi-select").getMessage());
select = extractImplementation(selector);
if (select == null)
return false;
return new PreparedQueryCacheImpl.StrongExclusion(_id, _loc.get("exclude-no-select").getMessage());
SQLBuffer buffer = selector.getSQL();
if (buffer == null)
return false;
return new PreparedQueryCacheImpl.StrongExclusion(_id, _loc.get("exclude-no-sql").getMessage());;
setTargetQuery(buffer.getSQL());
setParameters(buffer.getParameters());
setUserParameterPositions(buffer.getUserParameters());
_initialized = true;
return true;
return null;
}
/**
* Extract the underlying SelectExecutor from the given argument, if possible.
*
* @return two objects in an array. The element at index 0 is SelectExecutor,
* if it can be extracted. The element at index 1 is the reason why it can
* not be extracted.
*/
private SelectExecutor extractSelectExecutor(Object result) {
private Object[] extractSelectExecutor(Object result) {
if (result instanceof ResultList == false)
return null;
return new Object[]{null, _loc.get("exclude-not-result")};
Object userObject = ((ResultList<?>)result).getUserObject();
if (userObject == null || !userObject.getClass().isArray() || ((Object[])userObject).length != 2)
return null;
return new Object[]{null, _loc.get("exclude-no-user-object")};
Object provider = ((Object[])userObject)[0];
Object executor = ((Object[])userObject)[1];
if (executor instanceof StoreQuery.Executor == false)
return null;
return new Object[]{null, _loc.get("exclude-not-executor")};
_exps = ((StoreQuery.Executor)executor).getQueryExpressions();
if (_exps[0].projections.length == 0) {
_projTypes = StoreQuery.EMPTY_CLASSES;
@ -202,9 +213,9 @@ public class PreparedQueryImpl implements PreparedQuery {
provider = ((QueryImpl.PackingResultObjectProvider)provider).getDelegate();
}
if (provider instanceof SelectResultObjectProvider) {
return ((SelectResultObjectProvider)provider).getSelect();
return new Object[]{((SelectResultObjectProvider)provider).getSelect(), null};
}
return null;
return new Object[]{null, _loc.get("exclude-not-select-rop")};
}
private SelectImpl extractImplementation(SelectExecutor selector) {

View File

@ -113,15 +113,16 @@ graph-not-cycle-free: A circular flush dependency has been found after all \
batch_limit: The batch limit is set to {0}.
batch_update_info: ExecuteBatch command returns update count {0} for \
statement {1}.
strong-exclusion: excluded permanently
weak-exclusion: excluded temporarily
prepared-query-excluded-by-user: because matches user specified exclusion \
pattern "{0}"
prepared-query-cached: Query "{0}" is cached as target query "{1}"
prepared-query-not-cachable: Query "{0}" is not fit for caching.
prepared-query-invalidate: Query "{0}" is invalidated and removed from cache.
prepared-query-uncache-strong: Query "{0}" is permanently excluded from cache.
prepared-query-uncache-weak: Query "{0}" is excluded temporarily due to "{1}".
prepared-query-add-pattern: Adding a Query exclusion pattern "{0}" has caused \
following {1} cached queries to be removed from the cache: "{2}".
prepared-query-remove-pattern: Removing a Query exclusion pattern "{0}" caused \
following {1} queries to be re-inserted in the cache: "{2}".
prepared-query-uncache: Query "{0}" is removed from cache {1}.
prepared-query-remove-pattern: Removing exclusion pattern "{0}" caused \
query to be cacheable again.
uparam-mismatch: Supplied user parameters "{1}" do not match expected \
parameters "{0}" for the prepared query "{2}".
uparam-null: No user parameter was given. Expected parameters "{0}" for the \
@ -152,3 +153,16 @@ bad-lrs-size: Invalid LRS size. Valid values are \
"unknown"(0), "last"(1) or "query"(2). Specified value: {0}.
bad-join-syntax: Invalid join syntax. Valid values are \
"sql92"(0), "tradition"(1) or "database"(2). Specified value: {0}.
exclude-multi-select: because this query generates multiple SQL statements. \
A query can be cached only when it corresponds to a single SQL statement.
exclude-not-result: because this query returns a single value rather than \
a list. A query that returns single value can not be cached.
exclude-no-user-object: because post-execution data can not be extracted \
from this query.
exclude-no-sql: because target SQL statement can not be extracted \
from this query.
exclude-no-select: because internal select instance can not be extracted \
from this query.
exclude-no-select-rop: because the query result is not obtained by executing \
a select statement. This can happen if the query was evaluated in-memory.
exclude-not-executor: because this query was not executed on a data store.

View File

@ -20,6 +20,8 @@ package org.apache.openjpa.kernel;
import java.util.Map;
import org.apache.openjpa.kernel.PreparedQueryCache.Exclusion;
/**
* A prepared query associates a compiled query to a <em>parsed state</em> that
* can be executed possibly with more efficiency. An obvious example is to
@ -86,10 +88,10 @@ public interface PreparedQuery {
* @param o an opaque instance supposed to carry post-execution data such
* as target database query, parameters of the query etc.
*
* @return true if this receiver can initialize itself from the given
* @return Exclusion if this receiver can initialize itself from the given
* argument. false otherwise.
*/
public boolean initialize(Object o);
public Exclusion initialize(Object o);
/**
* Affirms if this receiver has been initialized.

View File

@ -18,6 +18,7 @@
*/
package org.apache.openjpa.kernel;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
@ -55,8 +56,8 @@ import org.apache.openjpa.lib.conf.Configurable;
*
* This cache allows customization of whether a query can be cached or not
* via either explicit marking of certain keys as non-cachable (which is
* irreversible) or addition/removal of exclusion patterns (which is
* reversible).
* irreversible or <em>strong</em>) or addition/removal of exclusion patterns
* (which is reversible or <em>weak</em>).
*
* @see #markUncachable(String)
* @see #addExclusionPattern(String)
@ -143,25 +144,28 @@ public interface PreparedQueryCache extends Configurable {
/**
* Marks the given key as not amenable to caching.
* Explicit marking helps to avoid repeated computational cost of
* determining whether a query can be cached or not.
* Explicit marking helps to avoid repeated computational cost of
* determining whether a query can be cached or not.
*
* Explicit marking can not be reversed by removal of exclusion patterns.
* @param id is the key to be excluded
* @param exclusion directs whether exclusion is irreversible or not.
*
* @return The value for the given key if it had been cached before. null
* otherwise.
*/
public PreparedQuery markUncachable(String id);
public PreparedQuery markUncachable(String id, Exclusion exclusion);
/**
* Affirms if the given key matches any of the exclusion patterns.
* Returns the exclusion status of if the given query key.
*
* @return null implies that the key is not excluded.
*/
public boolean isExcluded(String id);
public Exclusion isExcluded(String id);
/**
* Gets the exclusion patterns.
*/
public List<String> getExcludes();
public List<Exclusion> getExcludes();
/**
* Sets one or more exclusion regular expression patterns separated by
@ -182,7 +186,7 @@ public interface PreparedQueryCache extends Configurable {
* Any excluded key that matches the given pattern can now be cached
* again, unless it has been marked non-cachable explicitly.
*
* @see #markUncachable(String)
* @see #markUncachable(String, Exclusion)
*/
public void removeExclusionPattern(String pattern);
@ -195,4 +199,31 @@ public interface PreparedQueryCache extends Configurable {
* Gets the simple statistics for executed queries.
*/
public QueryStatistics<String> getStatistics();
/**
* A structure to describe the strength and reason for excluding a query from the cache.
*
*/
public static interface Exclusion {
/**
* Affirms if this exclusion is strong i.e. can never be reversed.
*/
public boolean isStrong();
/**
* Gets the human-readable reason for excluding this query from being cached.
*/
public String getReason();
/**
* The pattern (either the exact query string or a regular expression) that
* denotes this exclusion.
*/
public String getPattern();
/**
* Affirms if this exclusion matches the given identifier.
*/
boolean matches(String id);
}
}

View File

@ -417,3 +417,7 @@ bad-lock-level: Invalid lock mode/level. Valid values are \
"optimistic-force-increment"(25), "pessimistic-read"(30), \
"pessimistic-write"(40) or "pessimistic-force-increment"(50). \
Specified value: {0}.
declared-unbound-params: User specified parameter "{1}" does not appear in \
declared parameters "{2}" of the query "{0}".
user-unbound-params: User has not bound parameter "{1}" for the query "{0}". \
The declared parameters are "{2}", bound parameters are "{3}".

View File

@ -59,7 +59,10 @@ public class TestFinderCache extends SQLListenerTestCase {
}
public void setUp() {
super.setUp(CLEAR_TABLES, Merchandise.class, Book.class, CD.class,
super.setUp(CLEAR_TABLES,
"openjpa.RuntimeUnenhancedClasses", "unsupported",
"openjpa.DynamicEnhancementAgent", "false",
Merchandise.class, Book.class, CD.class,
Author.class, Person.class, Singer.class, Address.class);
createTestData();
}

View File

@ -18,6 +18,7 @@
*/
package org.apache.openjpa.persistence.jdbc.sqlcache;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
@ -55,10 +56,14 @@ public class TestPreparedQueryCacheExclusion extends TestCase {
String excludes = "a;b;c";
cache.setExcludes(excludes);
assertEquals(3, cache.getExcludes().size());
assertTrue(cache.isExcluded("a"));
assertTrue(cache.isExcluded("b"));
assertTrue(cache.isExcluded("c"));
assertFalse(cache.isExcluded("d"));
assertNotNull(cache.isExcluded("a"));
assertNotNull(cache.isExcluded("b"));
assertNotNull(cache.isExcluded("c"));
assertNull(cache.isExcluded("d"));
List<PreparedQueryCache.Exclusion> exclusions = cache.getExcludes();
for (PreparedQueryCache.Exclusion e : exclusions)
System.err.println(e);
}
public void testCachePopulationSetUp() {
@ -106,7 +111,8 @@ public class TestPreparedQueryCacheExclusion extends TestCase {
public void testRemoveExclusionPatternDoesNotRemoveUserProhbitedKeys() {
String USER_MARKED_UNCACHABLE = "[user prohibited]";
cache.markUncachable(USER_MARKED_UNCACHABLE);
cache.markUncachable(USER_MARKED_UNCACHABLE,
new PreparedQueryCacheImpl.StrongExclusion(USER_MARKED_UNCACHABLE,"for testing"));
PreparedQuery p = new PreparedQueryImpl(USER_MARKED_UNCACHABLE, "xyz",
null);