| /* |
| * 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.jdbc.kernel; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.openjpa.jdbc.meta.ClassMapping; |
| import org.apache.openjpa.jdbc.meta.FieldMapping; |
| import org.apache.openjpa.jdbc.meta.MappingRepository; |
| import org.apache.openjpa.jdbc.schema.Column; |
| import org.apache.openjpa.jdbc.sql.LogicalUnion; |
| import org.apache.openjpa.jdbc.sql.SQLBuffer; |
| import org.apache.openjpa.jdbc.sql.SelectExecutor; |
| import org.apache.openjpa.jdbc.sql.SelectImpl; |
| import org.apache.openjpa.jdbc.sql.Union; |
| import org.apache.openjpa.kernel.Broker; |
| import org.apache.openjpa.kernel.PreparedQuery; |
| import org.apache.openjpa.kernel.PreparedQueryCache.Exclusion; |
| 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.exps.Parameter; |
| import org.apache.openjpa.kernel.exps.QueryExpressions; |
| import org.apache.openjpa.lib.rop.RangeResultObjectProvider; |
| import org.apache.openjpa.lib.rop.ResultList; |
| import org.apache.openjpa.lib.util.Localizer; |
| import org.apache.openjpa.meta.FieldMetaData; |
| import org.apache.openjpa.util.ImplHelper; |
| import org.apache.openjpa.util.InternalException; |
| import org.apache.openjpa.util.UserException; |
| |
| /** |
| * Implements {@link PreparedQuery} for SQL queries. |
| * PreparedQuery holds the post-compilation and post-execution state of a kernel Query. |
| * The post-execution internal state of a query is appended as a <em>user object</em> |
| * to the user-visible result to maintain the API contract. |
| * |
| * @author Pinaki Poddar |
| * |
| */ |
| public class PreparedQueryImpl implements PreparedQuery { |
| private static Localizer _loc = |
| Localizer.forPackage(PreparedQueryImpl.class); |
| |
| private final String _id; |
| private String _sql; |
| private boolean _initialized; |
| |
| // Post-compilation state of an executable query, populated on construction |
| private Class<?> _candidate; |
| private boolean _subclasses; |
| |
| // post-execution state of a query |
| private QueryExpressions[] _exps; |
| private Class<?>[] _projTypes; |
| |
| // Position of the user defined parameters in the _params list |
| private Map<Object, Integer[]> _userParamPositions; |
| private Map<Integer, Object> _template; |
| private SelectImpl select; |
| |
| /** |
| * Construct. |
| * |
| * @param id an identifier for this query to be used as cache key |
| * @param compiled a compiled query |
| */ |
| public PreparedQueryImpl(String id, Query compiled) { |
| this(id, null, compiled); |
| } |
| |
| /** |
| * Construct. |
| * |
| * @param id an identifier for this query to be used as cache key |
| * @param corresponding data store language query string |
| * @param compiled a compiled query |
| */ |
| public PreparedQueryImpl(String id, String sql, Query compiled) { |
| this._id = id; |
| this._sql = sql; |
| if (compiled != null) { |
| _candidate = compiled.getCandidateType(); |
| _subclasses = compiled.hasSubclasses(); |
| } |
| } |
| |
| @Override |
| public String getIdentifier() { |
| return _id; |
| } |
| |
| @Override |
| public String getLanguage() { |
| return QueryLanguages.LANG_PREPARED_SQL; |
| } |
| |
| /** |
| * Get the original query string which is same as the identifier of this |
| * receiver. |
| */ |
| @Override |
| public String getOriginalQuery() { |
| return getIdentifier(); |
| } |
| |
| @Override |
| public String getTargetQuery() { |
| return _sql; |
| } |
| |
| void setTargetQuery(String sql) { |
| _sql = sql; |
| } |
| |
| @Override |
| public boolean isInitialized() { |
| return _initialized; |
| } |
| |
| public QueryExpressions[] getQueryExpressions() { |
| return _exps; |
| } |
| |
| public Class[] getProjectionTypes() { |
| return _projTypes; |
| } |
| |
| /** |
| * Pours the post-compilation state held by this receiver to the given |
| * query. |
| */ |
| @Override |
| public void setInto(Query q) { |
| q.setQuery(_id); |
| q.setCandidateType(_candidate, _subclasses); |
| } |
| |
| /** |
| * Initialize this receiver with post-execution result. |
| * 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. |
| */ |
| @Override |
| public Exclusion initialize(Object result) { |
| if (isInitialized()) |
| 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 new PreparedQueryCacheImpl.StrongExclusion(_id, _loc.get("exclude-multi-select", _id).getMessage()); |
| select = extractImplementation(selector); |
| if (select == null) |
| return new PreparedQueryCacheImpl.StrongExclusion(_id, _loc.get("exclude-no-select", _id).getMessage()); |
| SQLBuffer buffer = selector.getSQL(); |
| if (buffer == null) |
| return new PreparedQueryCacheImpl.StrongExclusion(_id, _loc.get("exclude-no-sql", _id).getMessage()); |
| if (isUsingFieldStrategy()) |
| return new PreparedQueryCacheImpl.StrongExclusion(_id, |
| _loc.get("exclude-user-strategy", _id).getMessage()); |
| |
| if (isPaginated()) |
| return new PreparedQueryCacheImpl.StrongExclusion(_id, |
| _loc.get("exclude-pagination", _id).getMessage()); |
| |
| setTargetQuery(buffer.getSQL()); |
| setParameters(buffer.getParameters()); |
| setUserParameterPositions(buffer.getUserParameters()); |
| _initialized = 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 Object[] extractSelectExecutor(Object result) { |
| if (!(result instanceof ResultList)) |
| return new Object[]{null, _loc.get("exclude-not-result", _id)}; |
| Object userObject = ((ResultList<?>)result).getUserObject(); |
| if (userObject == null || !userObject.getClass().isArray() || ((Object[])userObject).length != 2) |
| return new Object[]{null, _loc.get("exclude-no-user-object", _id)}; |
| Object provider = ((Object[])userObject)[0]; |
| Object executor = ((Object[])userObject)[1]; |
| if (!(executor instanceof StoreQuery.Executor)) |
| return new Object[]{null, _loc.get("exclude-not-executor", _id)}; |
| _exps = ((StoreQuery.Executor)executor).getQueryExpressions(); |
| for (QueryExpressions exp : _exps) { |
| if (exp.hasInExpression) |
| return new Object[]{null, _loc.get("exclude-in-expression", _id)}; |
| if (isUsingExternalizedParameter(exp)) { |
| return new Object[]{null, _loc.get("exclude-externalized-param", _id)}; |
| } |
| } |
| if (_exps[0].projections.length == 0) { |
| _projTypes = StoreQuery.EMPTY_CLASSES; |
| } else { |
| _projTypes = new Class[_exps[0].projections.length]; |
| for (int i = 0; i < _exps[0].projections.length; i++) { |
| _projTypes[i] = _exps[0].projections[i].getType(); |
| } |
| } |
| if (provider instanceof QueryImpl.PackingResultObjectProvider) { |
| provider = ((QueryImpl.PackingResultObjectProvider)provider).getDelegate(); |
| } |
| if (provider instanceof RangeResultObjectProvider) { |
| provider = ((RangeResultObjectProvider)provider).getDelegate(); |
| } |
| if (provider instanceof SelectResultObjectProvider) { |
| return new Object[]{((SelectResultObjectProvider)provider).getSelect(), null}; |
| } |
| return new Object[]{null, _loc.get("exclude-not-select-rop", _id, provider.getClass().getName())}; |
| } |
| |
| private SelectImpl extractImplementation(SelectExecutor selector) { |
| if (selector == null) |
| return null; |
| if (selector instanceof SelectImpl) |
| return (SelectImpl)selector; |
| if (selector instanceof LogicalUnion.UnionSelect) |
| return ((LogicalUnion.UnionSelect)selector).getDelegate(); |
| if (selector instanceof Union) |
| return extractImplementation(((Union)selector).getSelects()[0]); |
| |
| return null; |
| } |
| |
| private boolean isUsingExternalizedParameter(QueryExpressions exp) { |
| if (exp == null) |
| return false; |
| List<FieldMetaData> fmds = exp.getParameterizedFields(); |
| if (fmds == null || fmds.isEmpty()) |
| return false; |
| for (FieldMetaData fmd : fmds) { |
| if (fmd.isExternalized()) |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean isPaginated() { |
| if (select instanceof SelectImpl) { |
| if (((SelectImpl)select).getStartIndex() != 0 || |
| ((SelectImpl)select).getEndIndex() != Long.MAX_VALUE) |
| return true; |
| } |
| return false; |
| } |
| private boolean isUsingFieldStrategy() { |
| for (QueryExpressions exp : _exps) { |
| if (isUsingFieldStrategy(exp)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean isUsingFieldStrategy(QueryExpressions exp) { |
| if (exp == null) |
| return false; |
| List<FieldMetaData> fmds = exp.getParameterizedFields(); |
| if (fmds == null || fmds.isEmpty()) |
| return false; |
| for (FieldMetaData fmd : fmds) { |
| if (((FieldMapping)fmd).getMappingInfo().getStrategy() != null) |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Merge the given user parameters with its own parameter. The given map |
| * must be compatible with the user parameters extracted during |
| * {@link #initialize(Object) initialization}. |
| * |
| * @return 0-based parameter index mapped to corresponding values. |
| * |
| */ |
| @Override |
| public Map<Integer, Object> reparametrize(Map user, Broker broker) { |
| if (!isInitialized()) |
| throw new InternalException("reparameterize() on uninitialized."); |
| if (user == null || user.isEmpty()) { |
| if (!_userParamPositions.isEmpty()) { |
| throw new UserException(_loc.get("uparam-null", |
| _userParamPositions.keySet(), this)); |
| } else { |
| return _template; |
| } |
| } |
| if (!_userParamPositions.keySet().equals(user.keySet())) { |
| throw new UserException(_loc.get("uparam-mismatch", |
| _userParamPositions.keySet(), user.keySet(), this)); |
| } |
| Map<Integer, Object> result = new HashMap<>(_template); |
| |
| Set<Map.Entry<Object,Object>> userSet = user.entrySet(); |
| for (Map.Entry<Object,Object> userEntry : userSet) { |
| Object key = userEntry.getKey(); |
| Integer[] indices = _userParamPositions.get(key); |
| if (indices == null || indices.length == 0) |
| throw new UserException(_loc.get("uparam-no-pos", key, this)); |
| Object val = userEntry.getValue(); |
| if (ImplHelper.isManageable(val)) { |
| setPersistenceCapableParameter(result, val, indices, broker); |
| } else if (val instanceof Collection) { |
| setCollectionValuedParameter(result, (Collection)val, indices, |
| key, broker); |
| } else { |
| for (int j : indices) { |
| if (val instanceof Enum) { |
| if (_template.get(j) instanceof Integer) { |
| val = ((Enum)val).ordinal(); |
| } else { |
| val = ((Enum)val).name(); |
| } |
| } |
| result.put(j, val); |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Calculate primary key identity value(s) of the given manageable instance |
| * and fill in the given map. |
| * |
| * @param values a map of integer parameter index to parameter value |
| * @param pc a manageable instance |
| * @param indices the indices of the column values |
| * @param broker used to obtain the primary key values |
| */ |
| private void setPersistenceCapableParameter(Map<Integer,Object> result, |
| Object pc, Integer[] indices, Broker broker) { |
| JDBCStore store = (JDBCStore)broker.getStoreManager() |
| .getInnermostDelegate(); |
| MappingRepository repos = store.getConfiguration() |
| .getMappingRepositoryInstance(); |
| ClassMapping mapping = repos.getMapping(pc.getClass(), |
| broker.getClassLoader(), true); |
| Column[] pks = mapping.getPrimaryKeyColumns(); |
| Object cols = mapping.toDataStoreValue(pc, pks, store); |
| if (cols instanceof Object[]) { |
| Object[] array = (Object[])cols; |
| int n = array.length; |
| if (n > indices.length || indices.length%n != 0) |
| throw new UserException(_loc.get("uparam-pc-key", |
| pc.getClass(), n, Arrays.toString(indices))); |
| int k = 0; |
| for (int j : indices) { |
| result.put(j, array[k%n]); |
| k++; |
| } |
| } else { |
| for (int j : indices) { |
| result.put(j, cols); |
| } |
| } |
| } |
| |
| private void setCollectionValuedParameter(Map<Integer,Object> result, |
| Collection values, Integer[] indices, Object param, Broker broker) { |
| int n = values.size(); |
| Object[] array = values.toArray(); |
| if (n == 0 || n > indices.length || indices.length%n != 0) { |
| throw new UserException(_loc.get("uparam-coll-size", param, values, |
| Arrays.toString(indices))); |
| } |
| int k = 0; |
| for (int j : indices) { |
| Object val = array[k%n]; |
| if (ImplHelper.isManageable(val)) |
| setPersistenceCapableParameter(result, val, indices, broker); |
| else |
| result.put(j, val); |
| k++; |
| } |
| |
| } |
| /** |
| * Marks the positions and keys of user parameters. |
| * |
| * @param list even elements are numbers representing the position of a |
| * user parameter in the _param list. Odd elements are the user parameter |
| * key. A user parameter key may appear more than once. |
| */ |
| void setUserParameterPositions(List list) { |
| _userParamPositions = new HashMap<>(); |
| List<Integer> positions = new ArrayList<>(); |
| for (int i = 1; list != null && i < list.size(); i += 2) { |
| Object key = ((Parameter)list.get(i)).getParameterKey(); |
| positions.clear(); |
| for (int j = 1; j < list.size(); j += 2) { |
| Object other = ((Parameter)list.get(j)).getParameterKey(); |
| if (key.equals(other)) |
| positions.add((Integer)list.get(j-1)); |
| } |
| _userParamPositions.put(key, positions.toArray(new Integer[positions.size()])); |
| } |
| } |
| |
| void setParameters(List list) { |
| Map<Integer, Object> tmp = new HashMap<>(); |
| for (int i = 0; list != null && i < list.size(); i++) { |
| tmp.put(i, list.get(i)); |
| } |
| _template = Collections.unmodifiableMap(tmp); |
| } |
| |
| SelectImpl getSelect() { |
| return select; |
| } |
| |
| @Override |
| public String toString() { |
| return "PreparedQuery: [" + getOriginalQuery() + "] --> [" + |
| getTargetQuery() + "]"; |
| } |
| } |