blob: ef740d4d0379561ea10f5dab265626e413e8ec95 [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.persistence;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.enhance.Reflection;
import org.apache.openjpa.kernel.FetchConfiguration;
import org.apache.openjpa.kernel.Filters;
import org.apache.openjpa.kernel.QueryHints;
import org.apache.openjpa.kernel.exps.AggregateListener;
import org.apache.openjpa.kernel.exps.FilterListener;
import org.apache.openjpa.lib.conf.ProductDerivation;
import org.apache.openjpa.lib.conf.ProductDerivations;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.StringDistance;
/**
* Manages query hint keys and handles their values on behalf of a owning
* {@link QueryImpl}. Uses specific knowledge of hint keys declared in
* different parts of the system.
*
* This receiver collects hint keys from different parts of the system. The
* keys are implicitly or explicitly declared by several different mechanics.
* This receiver sets the values on behalf of a owning {@link QueryImpl}
* based on the its specific knowledge of these keys.
*
* The hint keys from following sources are collected and handled:
*
* 1. {@link org.apache.openjpa.kernel.QueryHints} interface declares hint keys
* as public static final fields. These fields are collected by reflection.
* The values are handled by invoking methods on the owning {@link QueryImpl}
*
* 2. Some hint keys are collected from bean-style property names of {@link
* JDBCFetchPlan} by {@link Reflection#getBeanStylePropertyNames(Class)
* reflection} and prefixed with <code>openjpa.FetchPlan</code>.
* Their values are used to set the corresponding property of {@link
* FetchPlan} via {@link #hintToSetter(FetchPlan, String, Object) reflection}
*
* 3. Currently defined <code>javax.persistence.*</code> hint keys have
* a equivalent counterpart to one of these FetchPlan keys.
* The JPA keys are mapped to equivalent FetchPlan hint keys.
*
* 4. Some keys directly invoke setters or add listeners to the owning
* {@link QueryImpl}. These hint keys are statically declared in
* this receiver itself.
*
* 5. ProductDerivation may introduce their own query hint keys via {@link
* ProductDerivation#getSupportedQueryHints()}. Their values are set in the
* {@link FetchConfiguration#setHint(String, Object)}
*
* A hint key is classified into one of the following three categories:
*
* 1. Supported: A key is known to this receiver as collected from different
* parts of the system. The value of a supported key is recorded and
* available via {@link #getHints()} method.
* 2. Recognized: A key is not known to this receiver but has a prefix which
* is known to this receiver. The value of a recognized key is not recorded
* but its value is available via {@link FetchConfiguration#getHint(String)}
* 3. Unrecognized: A key is neither supported nor recognized. The value of a
* unrecognized key is neither recorded nor set anywhere.
*
* If an incompatible value is supplied for a supported key, a non-fatal
* {@link ArgumentException} is raised.
*
* @author Pinaki Poddar
*
* @since 2.0.0
*
*/
public class HintHandler {
protected final QueryImpl<?> owner;
private static final Localizer _loc = Localizer.forPackage(HintHandler.class);
protected static Set<String> _supportedHints = ProductDerivations.getSupportedQueryHints();
protected static final String PREFIX_OPENJPA = "openjpa.";
protected static final String PREFIX_JDBC = PREFIX_OPENJPA + "jdbc.";
protected static final String PREFIX_FETCHPLAN = PREFIX_OPENJPA + "FetchPlan.";
private Map<String, Object> _hints;
HintHandler(QueryImpl<?> impl) {
super();
owner = impl;
}
/**
* Record a key-value pair only only if the given key is supported.
*
* @return FALSE if the key is unrecognized.
* null (i.e. MAY BE) if the key is recognized, but not supported.
* TRUE if the key is supported.
*/
protected Boolean record(String hint, Object value) {
if (hint == null)
return Boolean.FALSE;
if (_supportedHints.contains(hint)) {
if (_hints == null)
_hints = new TreeMap<>();
_hints.put(hint, value);
return Boolean.TRUE;
}
if (isKnownPrefix(hint)) {
Log log = owner.getDelegate().getBroker().getConfiguration().getLog(OpenJPAConfiguration.LOG_RUNTIME);
String possible = StringDistance.getClosestLevenshteinDistance(hint, getSupportedHints());
if (log.isWarnEnabled())
log.warn(_loc.get("bad-query-hint", hint, possible));
return null; // possible but not registered
}
return Boolean.FALSE; // not possible
}
public void setHint(String key, Object value) {
Boolean status = record(key, value);
if (Boolean.FALSE.equals(status))
return;
FetchPlan plan = owner.getFetchPlan();
if (status == null) {
plan.setHint(key, value);
return;
}
ClassLoader loader = owner.getDelegate().getBroker().getClassLoader();
if (QueryHints.HINT_SUBCLASSES.equals(key)) {
if (value instanceof String)
value = Boolean.valueOf((String) value);
owner.setSubclasses((Boolean) value);
} else if (QueryHints.HINT_RELAX_BIND_PARAM_TYPE_CHECK.equals(key)) {
owner.setRelaxBindParameterTypeChecking(value);
} else if (QueryHints.HINT_FILTER_LISTENER.equals(key)) {
owner.addFilterListener(Filters.hintToFilterListener(value, loader));
} else if (QueryHints.HINT_FILTER_LISTENERS.equals(key)) {
FilterListener[] arr = Filters.hintToFilterListeners(value, loader);
for (int i = 0; i < arr.length; i++)
owner.addFilterListener(arr[i]);
} else if (QueryHints.HINT_AGGREGATE_LISTENER.equals(key)) {
owner.addAggregateListener(Filters.hintToAggregateListener(value, loader));
} else if (QueryHints.HINT_AGGREGATE_LISTENERS.equals(key)) {
AggregateListener[] arr = Filters.hintToAggregateListeners(value, loader);
for (int i = 0; i < arr.length; i++) {
owner.addAggregateListener(arr[i]);
}
} else if (QueryHints.HINT_RESULT_COUNT.equals(key)) {
int v = (Integer) Filters.convert(value, Integer.class);
if (v < 0) {
throw new IllegalArgumentException(_loc.get("bad-query-hint-value", key, value).toString());
}
plan.setHint(key, v);
} else if (QueryHints.HINT_INVALIDATE_PREPARED_QUERY.equals(key)) {
plan.setHint(key, Filters.convert(value, Boolean.class));
owner.invalidatePreparedQuery();
} else if (QueryHints.HINT_IGNORE_PREPARED_QUERY.equals(key)) {
plan.setHint(key, Filters.convert(value, Boolean.class));
owner.ignorePreparedQuery();
} else if (QueryHints.HINT_USE_LITERAL_IN_SQL.equals(key)) {
Boolean convertedValue = (Boolean)Filters.convert(value, Boolean.class);
plan.setHint(key, convertedValue);
} else { // default
plan.setHint(key, value);
}
}
/**
* Affirms if the given key starts with any of the known prefix.
* @param key
*/
protected boolean isKnownPrefix(String key) {
if (key == null)
return false;
for (String prefix : ProductDerivations.getConfigurationPrefixes()) {
if (key.startsWith(prefix))
return true;
}
return false;
}
// protected boolean hasPrecedent(String key) {
// boolean hasPrecedent = true;
// String[] list = precedenceMap.get(key);
// if (list != null) {
// for (String hint : list) {
// if (hint.equals(key))
// break;
// // stop if a higher precedence hint has already defined
// if (getHints().containsKey(hint)) {
// hasPrecedent = false;
// break;
// }
// }
// }
// return hasPrecedent;
// }
// private Integer toLockLevel(Object value) {
// Object origValue = value;
// if (value instanceof String) {
// // to accommodate alias name input in relationship with enum values
// // e.g. "optimistic-force-increment" == LockModeType.OPTIMISTIC_FORCE_INCREMENT
// String strValue = ((String) value).toUpperCase().replace('-', '_');
// value = Enum.valueOf(LockModeType.class, strValue);
// }
// if (value instanceof LockModeType)
// value = MixedLockLevelsHelper.toLockLevel((LockModeType) value);
//
// Integer intValue = null;
// if (value instanceof Integer)
// intValue = (Integer) value;
// if (intValue == null
// || (intValue != MixedLockLevels.LOCK_NONE
// && intValue != MixedLockLevels.LOCK_READ
// && intValue != MixedLockLevels.LOCK_OPTIMISTIC
// && intValue != MixedLockLevels.LOCK_WRITE
// && intValue != MixedLockLevels.LOCK_OPTIMISTIC_FORCE_INCREMENT
// && intValue != MixedLockLevels.LOCK_PESSIMISTIC_READ
// && intValue != MixedLockLevels.LOCK_PESSIMISTIC_WRITE
// && intValue != MixedLockLevels.LOCK_PESSIMISTIC_FORCE_INCREMENT)
// )
// throw new IllegalArgumentException(_loc.get("bad-lock-level", origValue).getMessage());
// return intValue;
// }
/**
* Gets all the supported hint keys. The set of supported hint keys is
* statically determined by collecting hint keys from the ProductDerivations.
*/
public Set<String> getSupportedHints() {
return _supportedHints;
}
/**
* Gets all the recorded hint keys and their values.
*/
public Map<String, Object> getHints() {
if (_hints == null)
return Collections.emptyMap();
return Collections.unmodifiableMap(_hints);
}
}