blob: 711e6ac6c9d3fa81b76db1fe2aa710d8dedee89c [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.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.persistence.Parameter;
import javax.persistence.TemporalType;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.ParameterExpression;
import org.apache.openjpa.kernel.Filters;
import org.apache.openjpa.kernel.QueryLanguages;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.lib.util.OrderedMap;
import org.apache.openjpa.meta.QueryMetaData;
import org.apache.openjpa.persistence.criteria.BindableParameter;
/**
* An abstract implementation of the Query interface.
*/
public abstract class AbstractQuery<X> implements OpenJPAQuerySPI<X> {
private static final Localizer _loc = Localizer.forPackage(AbstractQuery.class);
protected boolean _relaxBindParameterTypeChecking;
protected boolean _convertPositionalParams;
// Will be null if this isn't a NamedQuery
protected final QueryMetaData _qmd;
protected transient EntityManagerImpl _em;
protected Map<Parameter<?>, Object> _boundParams;
protected Map<Object, Parameter<?>> _declaredParams;
public AbstractQuery(QueryMetaData qmd, EntityManagerImpl em) {
_qmd = qmd;
_em = em;
_boundParams = new HashMap<>();
}
/**
* Gets a map of values of each parameter indexed by their <em>original</em> key.
*
* @return an empty map if no parameter is declared for this query. The unbound parameters has a value of null which
* is indistinguishable from the value being bound to null.
*/
Map<Object, Object> getParameterValues() {
Map<Object, Object> result = new HashMap<>();
if (_boundParams == null)
return result;
for (Map.Entry<Object, Parameter<?>> entry : getDeclaredParameters().entrySet()) {
Object paramKey = entry.getKey();
Parameter<?> param = entry.getValue();
result.put(paramKey, _boundParams.get(param));
}
return result;
}
public boolean isProcedure() {
return QueryLanguages.LANG_STORED_PROC.equals(getLanguage());
}
public boolean isNative() {
return QueryLanguages.LANG_SQL.equals(getLanguage());
}
protected abstract void assertOpen();
protected abstract void lock();
protected abstract void unlock();
/**
* @return a map of parameter name to type for this query.
*/
protected abstract OrderedMap<Object, Class<?>> getParamTypes();
// =================================================================================
// Parameter processing routines
// =================================================================================
/**
* Binds the parameter identified by the given position to the given value. The parameter are bound to a value in
* the context of this query. The same parameter may be bound to a different value in the context of another
* query. <br>
* For non-native queries, the given position must be a valid position in the declared parameters. <br>
* As native queries may not be parsed and hence their declared parameters may not be known, setting an positional
* parameter has the side-effect of a positional parameter being declared.
*
* @param position
* positive, integer position of the parameter
* @param value
* an assignment compatible value
* @return the same query instance
* @throws IllegalArgumentException
* if position does not correspond to a positional parameter of the query or if the argument is of
* incorrect type
*/
@Override
public OpenJPAQuery<X> setParameter(int pos, Object value) {
if (_convertPositionalParams) {
return setParameter("_" + pos, value);
}
assertOpen();
_em.assertNotCloseInvoked();
lock();
try {
if (pos < 1) {
throw new IllegalArgumentException(_loc.get("illegal-index", pos).getMessage());
}
Parameter<?> param;
if (isNative() || isProcedure()) {
param = new ParameterImpl<>(pos, Object.class);
declareParameter(pos, param);
} else {
param = getParameter(pos);
}
bindValue(param, value);
return this;
} finally {
unlock();
}
}
/**
* Sets the value of the given positional parameter after conversion of the given value to the given Temporal Type.
*/
@Override
public OpenJPAQuery<X> setParameter(int position, Calendar value, TemporalType t) {
return setParameter(position, convertTemporalType(value, t));
}
/**
* Sets the value of the given named parameter after conversion of the given value to the given Temporal Type.
*/
@Override
public OpenJPAQuery<X> setParameter(int position, Date value, TemporalType type) {
return setParameter(position, convertTemporalType(value, type));
}
/**
* Converts the given Date to a value corresponding to given temporal type.
*/
Object convertTemporalType(Date value, TemporalType type) {
switch (type) {
case DATE:
return value;
case TIME:
return new Time(value.getTime());
case TIMESTAMP:
return new Timestamp(value.getTime());
default:
return null;
}
}
Object convertTemporalType(Calendar value, TemporalType type) {
return convertTemporalType(value.getTime(), type);
}
/**
* Affirms if declared parameters use position identifier.
*/
@Override
public boolean hasPositionalParameters() {
return !getDeclaredParameterKeys(Integer.class).isEmpty();
}
/**
* Gets the array of positional parameter values. The n-th array element represents (n+1)-th positional parameter.
* If a parameter has been declared but not bound to a value then the value is null and hence is indistinguishable
* from the value being actually null. If the parameter indexing is not contiguous then the unspecified parameters
* are considered as null.
*/
@Override
public Object[] getPositionalParameters() {
lock();
try {
Set<Integer> positionalKeys = getDeclaredParameterKeys(Integer.class);
Object[] result = new Object[calculateMaxKey(positionalKeys)];
for (Integer pos : positionalKeys) {
Parameter<?> param = getParameter(pos);
result[pos - 1] = isBound(param) ? getParameterValue(pos) : null;
}
return result;
} finally {
unlock();
}
}
/**
* Calculate the maximum value of the given set.
*/
int calculateMaxKey(Set<Integer> p) {
if (p == null)
return 0;
int max = Integer.MIN_VALUE;
for (Integer i : p)
max = Math.max(max, i);
return max;
}
/**
* Binds the given values as positional parameters. The n-th array element value is set to a Parameter with (n+1)-th
* positional identifier.
*/
@Override
public OpenJPAQuery<X> setParameters(Object... params) {
assertOpen();
_em.assertNotCloseInvoked();
lock();
try {
clearBinding();
for (int i = 0; params != null && i < params.length; i++) {
setParameter(i + 1, params[i]);
}
return this;
} finally {
unlock();
}
}
void clearBinding() {
if (_boundParams != null)
_boundParams.clear();
}
/**
* Gets the value of all the named parameters.
*
* If a parameter has been declared but not bound to a value then the value is null and hence is indistinguishable
* from the value being actually null.
*/
@Override
public Map<String, Object> getNamedParameters() {
lock();
try {
Map<String, Object> result = new HashMap<>();
Set<String> namedKeys = getDeclaredParameterKeys(String.class);
for (String name : namedKeys) {
Parameter<?> param = getParameter(name);
result.put(name, isBound(param) ? getParameterValue(name) : null);
}
return result;
} finally {
unlock();
}
}
/**
* Sets the values of the parameters from the given Map. The keys of the given map designate the name of the
* declared parameter.
*/
@Override
public OpenJPAQuery<X> setParameters(Map params) {
assertOpen();
_em.assertNotCloseInvoked();
lock();
try {
clearBinding();
if (params != null)
for (Map.Entry e : (Set<Map.Entry>) params.entrySet())
setParameter((String) e.getKey(), e.getValue());
return this;
} finally {
unlock();
}
}
/**
* Get the parameter of the given name and type.
*
* @throws IllegalArgumentException
* if the parameter of the specified name does not exist or is not assignable to the type
* @throws IllegalStateException
* if invoked on a native query
*/
@Override
public <T> Parameter<T> getParameter(String name, Class<T> type) {
Parameter<?> param = getParameter(name);
if (param.getParameterType().isAssignableFrom(type))
throw new IllegalArgumentException(param + " does not match the requested type " + type);
return (Parameter<T>) param;
}
/**
* Get the positional parameter with the given position and type.
*
* @throws IllegalArgumentException
* if the parameter with the specified position does not exist or is not assignable to the type
* @throws IllegalStateException
* if invoked on a native query unless the same parameter position is bound already.
*/
@Override
public <T> Parameter<T> getParameter(int pos, Class<T> type) {
if (_convertPositionalParams) {
return getParameter("_" + pos, type);
}
Parameter<?> param = getParameter(pos);
if (param.getParameterType().isAssignableFrom(type))
throw new IllegalArgumentException(param + " does not match the requested type " + type);
return (Parameter<T>) param;
}
/**
* Return the value bound to the parameter.
*
* @param param
* parameter object
* @return parameter value
* @throws IllegalStateException
* if the parameter has not been been bound
* @throws IllegalArgumentException
* if the parameter does not belong to this query
*/
@Override
public <T> T getParameterValue(Parameter<T> p) {
if (!isBound(p)) {
throw new IllegalArgumentException(_loc.get("param-missing", p, getQueryString(), getBoundParameterKeys())
.getMessage());
}
return (T) _boundParams.get(p);
}
/**
* Gets the parameters declared in this query.
*/
@Override
public Set<Parameter<?>> getParameters() {
Set<Parameter<?>> result = new HashSet<>();
result.addAll(getDeclaredParameters().values());
return result;
}
@Override
public <T> OpenJPAQuery<X> setParameter(Parameter<T> p, T arg1) {
bindValue(p, arg1);
if (BindableParameter.class.isInstance(p)) {
BindableParameter.class.cast(p).setValue(arg1);
}
return this;
}
@Override
public OpenJPAQuery<X> setParameter(Parameter<Date> p, Date date, TemporalType type) {
return setParameter(p, (Date) convertTemporalType(date, type));
}
@Override
public TypedQuery<X> setParameter(Parameter<Calendar> p, Calendar cal, TemporalType type) {
return setParameter(p, (Calendar) convertTemporalType(cal, type));
}
/**
* Get the parameter object corresponding to the declared parameter of the given name. This method is not required
* to be supported for native queries.
*
* @throws IllegalArgumentException
* if the parameter of the specified name does not exist
* @throws IllegalStateException
* if invoked on a native query
*/
@Override
public Parameter<?> getParameter(String name) {
if (isNative()) {
throw new IllegalStateException(_loc.get("param-named-non-native", name).getMessage());
}
Parameter<?> param = getDeclaredParameters().get(name);
if (param == null) {
Set<ParameterExpression> exps = getDeclaredParameterKeys(ParameterExpression.class);
for (ParameterExpression<?> e : exps) {
if (name.equals(e.getName()))
return e;
}
throw new IllegalArgumentException(_loc.get("param-missing-name", name, getQueryString(),
getDeclaredParameterKeys()).getMessage());
}
return param;
}
/**
* Get the positional parameter with the given position. The parameter may just have been declared and not bound to
* a value.
*
* @param position
* specified in the user query.
* @return parameter object
* @throws IllegalArgumentException
* if the parameter with the given position does not exist
*/
@Override
public Parameter<?> getParameter(int pos) {
if (_convertPositionalParams) {
return getParameter("_" + pos);
}
Parameter<?> param = getDeclaredParameters().get(pos);
if (param == null)
throw new IllegalArgumentException(_loc.get("param-missing-pos", pos, getQueryString(),
getDeclaredParameterKeys()).getMessage());
return param;
}
/**
* Return the value bound to the parameter.
*
* @param name
* name of the parameter
* @return parameter value
*
* @throws IllegalStateException
* if this parameter has not been bound
*/
@Override
public Object getParameterValue(String name) {
return _boundParams.get(getParameter(name));
}
/**
* Return the value bound to the parameter.
*
* @param pos
* position of the parameter
* @return parameter value
*
* @throws IllegalStateException
* if this parameter has not been bound
*/
@Override
public Object getParameterValue(int pos) {
Parameter<?> param = getParameter(pos);
assertBound(param);
return _boundParams.get(param);
}
/**
* Gets the parameter keys bound with this query. Parameter key can be Integer, String or a ParameterExpression
* itself but all parameters keys of a particular query are of the same type.
*/
public Set<?> getBoundParameterKeys() {
if (_boundParams == null)
return Collections.EMPTY_SET;
getDeclaredParameters();
Set<Object> result = new HashSet<>();
for (Map.Entry<Object, Parameter<?>> entry : _declaredParams.entrySet()) {
if (isBound(entry.getValue())) {
result.add(entry.getKey());
}
}
return result;
}
/**
* Gets the declared parameter keys in the given query. This information is only available after the query has been
* parsed. As native language queries are not parsed, this information is not available for them.
*
* @return set of parameter identifiers in a parsed query
*/
public Set<?> getDeclaredParameterKeys() {
return getDeclaredParameters().keySet();
}
public <T> Set<T> getDeclaredParameterKeys(Class<T> keyType) {
Set<T> result = new HashSet<>();
for (Object key : getDeclaredParameterKeys()) {
if (keyType.isInstance(key))
result.add((T) key);
}
return result;
}
/**
* Gets the parameter instances declared in this query. All parameter keys are of the same type. It is not allowed
* to mix keys of different type such as named and positional keys.
*
* For string-based queries, the parser supplies the information about the declared parameters as a LinkedMap of
* expected parameter value type indexed by parameter identifier. For non string-based queries that a facade itself
* may construct (e.g. CriteriaQuery), the parameters must be declared by the caller. This receiver constructs
* concrete Parameter instances from the given parameter identifiers.
*
* @return a Map where the key represents the original identifier of the parameter (can be a String, Integer or a
* ParameterExpression itself) and the value is the concrete Parameter instance either constructed as a
* result of this call or supplied by declaring the parameter explicitly via
* {@linkplain #declareParameter(Parameter)}.
*/
public Map<Object, Parameter<?>> getDeclaredParameters() {
if (_declaredParams == null) {
_declaredParams = new HashMap<>();
OrderedMap<Object, Class<?>> paramTypes = null;
// Check to see if we have a cached version of the paramTypes in QueryMetaData.
if (_qmd != null) {
paramTypes = _qmd.getParamTypes();
}
if (paramTypes == null) {
paramTypes = getParamTypes();
// Cache the param types as they haven't been set yet.
if (_qmd != null) {
_qmd.setParamTypes(paramTypes);
}
}
for (Entry<Object, Class<?>> entry : paramTypes.entrySet()) {
Object key = entry.getKey();
Class<?> expectedValueType = entry.getValue();
Parameter<?> param;
if (key instanceof Integer) {
param = new ParameterImpl((Integer) key, expectedValueType);
} else if (key instanceof String) {
param = new ParameterImpl((String) key, expectedValueType);
} else if (key instanceof Parameter) {
param = (Parameter<?>) key;
} else {
throw new IllegalArgumentException("parameter identifier " + key + " unrecognized");
}
declareParameter(key, param);
}
}
return _declaredParams;
}
/**
* Declares the given parameter for this query. Used by non-string based queries that are constructed by the facade
* itself rather than OpenJPA parsing the query to detect the declared parameters.
*
* @param key
* this is the key to identify the parameter later in the context of this query. Valid key types are
* Integer, String or ParameterExpression itself.
* @param the
* parameter instance to be declared
*/
public void declareParameter(Object key, Parameter<?> param) {
if (_declaredParams == null) {
_declaredParams = new HashMap<>();
}
_declaredParams.put(key, param);
}
/**
* Affirms if the given parameter is bound to a value for this query.
*/
@Override
public boolean isBound(Parameter<?> param) {
return _boundParams != null && _boundParams.containsKey(param);
}
void assertBound(Parameter<?> param) {
if (!isBound(param)) {
throw new IllegalStateException(_loc.get("param-not-bound", param, getQueryString(),
getBoundParameterKeys()).getMessage());
}
}
/**
* Binds the given value to the given parameter. Validates if the parameter can accept the value by its type.
*/
void bindValue(Parameter<?> param, Object value) {
Object bindVal = assertValueAssignable(param, value);
_boundParams.put(param, bindVal);
}
@Override
public OpenJPAQuery<X> setParameter(String name, Calendar value, TemporalType type) {
return setParameter(name, convertTemporalType(value, type));
}
@Override
public OpenJPAQuery<X> setParameter(String name, Date value, TemporalType type) {
return setParameter(name, convertTemporalType(value, type));
}
/**
* Sets the parameter of the given name to the given value.
*/
@Override
public OpenJPAQuery<X> setParameter(String name, Object value) {
assertOpen();
_em.assertNotCloseInvoked();
lock();
try {
// native queries can not have named parameters
if (isNative()) {
throw new IllegalArgumentException(_loc.get("no-named-params", name, getQueryString()).toString());
} else {
bindValue(getParameter(name), value);
}
return this;
} finally {
unlock();
}
}
/**
* Convert the given value to match the given parameter type, if possible.
*
* @param param
* a query parameter
* @param v
* a user-supplied value for the parameter
*/
Object assertValueAssignable(Parameter<?> param, Object v) {
Class<?> expectedType = param.getParameterType();
if (v == null) {
if (expectedType.isPrimitive())
throw new IllegalArgumentException(_loc.get("param-null-primitive", param).getMessage());
return v;
}
if (getRelaxBindParameterTypeChecking()) {
try {
return Filters.convert(v, expectedType);
} catch (Exception e) {
throw new IllegalArgumentException(_loc.get("param-type-mismatch",
new Object[] { param, getQueryString(), v, v.getClass().getName(), expectedType.getName() })
.getMessage());
}
} else {
if (!Filters.canConvert(v.getClass(), expectedType, true)) {
throw new IllegalArgumentException(_loc.get("param-type-mismatch",
new Object[] { param, getQueryString(), v, v.getClass().getName(), expectedType.getName() })
.getMessage());
} else {
return v;
}
}
}
// ================== End of Parameter Processing routines ================================
@Override
public boolean getRelaxBindParameterTypeChecking() {
return _relaxBindParameterTypeChecking;
}
@Override
public void setRelaxBindParameterTypeChecking(Object value) {
if (value != null) {
if (value instanceof String) {
_relaxBindParameterTypeChecking = "true".equalsIgnoreCase(value.toString());
} else if (value instanceof Boolean) {
_relaxBindParameterTypeChecking = (Boolean) value;
}
}
}
}