| /* |
| * 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 == true) { |
| return setParameter("_" + String.valueOf(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.intValue() - 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 == true) { |
| return getParameter("_" + String.valueOf(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 == true) { |
| return getParameter("_" + String.valueOf(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).booleanValue(); |
| } |
| } |
| } |
| } |