| /* |
| * 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); |
| } |
| |
| } |
| |