blob: 44f0388db1018551c98956b5b662cd672c174b11 [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 static org.apache.openjpa.kernel.QueryLanguages.LANG_PREPARED_SQL;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import org.apache.openjpa.conf.Compatibility;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.kernel.Broker;
import org.apache.openjpa.kernel.DelegatingQuery;
import org.apache.openjpa.kernel.DelegatingResultList;
import org.apache.openjpa.kernel.DistinctResultList;
import org.apache.openjpa.kernel.FetchConfiguration;
import org.apache.openjpa.kernel.PreparedQuery;
import org.apache.openjpa.kernel.PreparedQueryCache;
import org.apache.openjpa.kernel.QueryHints;
import org.apache.openjpa.kernel.QueryLanguages;
import org.apache.openjpa.kernel.QueryOperations;
import org.apache.openjpa.kernel.QueryStatistics;
import org.apache.openjpa.kernel.exps.AggregateListener;
import org.apache.openjpa.kernel.exps.FilterListener;
import org.apache.openjpa.kernel.jpql.JPQLParser;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.rop.ResultList;
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.OpenJPACriteriaBuilder;
import org.apache.openjpa.util.ImplHelper;
import org.apache.openjpa.util.RuntimeExceptionTranslator;
import org.apache.openjpa.util.UserException;
/**
* Implementation of {@link Query} interface.
*
* @author Marc Prud'hommeaux
* @author Abe White
*/
public class QueryImpl<X> extends AbstractQuery<X> implements Serializable {
private static final long serialVersionUID = 1L;
private static final Localizer _loc = Localizer.forPackage(QueryImpl.class);
private transient FetchPlan _fetch;
private String _id;
private transient ReentrantLock _lock = null;
private HintHandler _hintHandler;
private DelegatingQuery _query;
/**
* Constructor; supply factory exception translator and delegate.
*
* @param em The EntityManager which created this query
* @param ret Exception translator for this query
* @param query The underlying "kernel" query.
*/
public QueryImpl(EntityManagerImpl em, RuntimeExceptionTranslator ret, org.apache.openjpa.kernel.Query query,
QueryMetaData qmd) {
super(qmd, em);
_query = new DelegatingQuery(query, ret);
_lock = new ReentrantLock();
if(query.getLanguage() == QueryLanguages.LANG_SQL) {
_convertPositionalParams = false;
}
else {
Compatibility compat = query.getStoreContext().getConfiguration().getCompatibilityInstance();
_convertPositionalParams = compat.getConvertPositionalParametersToNamed();
}
}
/**
* Constructor; supply factory and delegate.
*
* @deprecated
*/
@Deprecated
public QueryImpl(EntityManagerImpl em, RuntimeExceptionTranslator ret, org.apache.openjpa.kernel.Query query) {
this(em, ret, query, null);
}
/**
* Constructor; supply factory and delegate.
*
* @deprecated
*/
@Deprecated
public QueryImpl(EntityManagerImpl em, org.apache.openjpa.kernel.Query query) {
this(em, null, query, null);
}
/**
* Delegate.
*/
public org.apache.openjpa.kernel.Query getDelegate() {
return _query.getDelegate();
}
@Override
public OpenJPAEntityManager getEntityManager() {
return _em;
}
@Override
public String getLanguage() {
return _query.getLanguage();
}
@Override
public QueryOperationType getOperation() {
return QueryOperationType.fromKernelConstant(_query.getOperation());
}
@Override
public FetchPlan getFetchPlan() {
_em.assertNotCloseInvoked();
_query.assertNotSerialized();
_query.lock();
try {
if (_fetch == null)
_fetch = ((EntityManagerFactoryImpl) _em
.getEntityManagerFactory()).toFetchPlan(_query
.getBroker(), _query.getFetchConfiguration());
return _fetch;
} finally {
_query.unlock();
}
}
@Override
public String getQueryString() {
String result = _query.getQueryString();
return result != null ? result : _id;
}
@Override
public boolean getIgnoreChanges() {
return _query.getIgnoreChanges();
}
@Override
public OpenJPAQuery<X> setIgnoreChanges(boolean ignore) {
_em.assertNotCloseInvoked();
_query.setIgnoreChanges(ignore);
return this;
}
@Override
public OpenJPAQuery<X> addFilterListener(FilterListener listener) {
_em.assertNotCloseInvoked();
_query.addFilterListener(listener);
return this;
}
@Override
public OpenJPAQuery<X> removeFilterListener(FilterListener listener) {
_em.assertNotCloseInvoked();
_query.removeFilterListener(listener);
return this;
}
@Override
public OpenJPAQuery<X> addAggregateListener(AggregateListener listener) {
_em.assertNotCloseInvoked();
_query.addAggregateListener(listener);
return this;
}
@Override
public OpenJPAQuery<X> removeAggregateListener(AggregateListener listener) {
_em.assertNotCloseInvoked();
_query.removeAggregateListener(listener);
return this;
}
@Override
public Collection<?> getCandidateCollection() {
return _query.getCandidateCollection();
}
@Override
public OpenJPAQuery<X> setCandidateCollection(Collection coll) {
_em.assertNotCloseInvoked();
_query.setCandidateCollection(coll);
return this;
}
@Override
public Class getResultClass() {
Class res = _query.getResultType();
if (res != null)
return res;
return _query.getCandidateType();
}
@Override
public OpenJPAQuery<X> setResultClass(Class cls) {
_em.assertNotCloseInvoked();
if (ImplHelper.isManagedType(_em.getConfiguration(), cls))
_query.setCandidateType(cls, true);
else
_query.setResultType(cls);
return this;
}
@Override
public boolean hasSubclasses() {
return _query.hasSubclasses();
}
@Override
public OpenJPAQuery<X> setSubclasses(boolean subs) {
_em.assertNotCloseInvoked();
Class<?> cls = _query.getCandidateType();
_query.setCandidateExtent(_query.getBroker().newExtent(cls, subs));
return this;
}
@Override
public int getFirstResult() {
return asInt(_query.getStartRange());
}
@Override
public OpenJPAQuery<X> setFirstResult(int startPosition) {
_em.assertNotCloseInvoked();
long end;
if (_query.getEndRange() == Long.MAX_VALUE)
end = Long.MAX_VALUE;
else
end = startPosition
+ (_query.getEndRange() - _query.getStartRange());
_query.setRange(startPosition, end);
return this;
}
@Override
public int getMaxResults() {
return asInt(_query.getEndRange() - _query.getStartRange());
}
@Override
public OpenJPAQuery<X> setMaxResults(int max) {
_em.assertNotCloseInvoked();
long start = _query.getStartRange();
if (max == Integer.MAX_VALUE)
_query.setRange(start, Long.MAX_VALUE);
else
_query.setRange(start, start + max);
return this;
}
@Override
public OpenJPAQuery<X> compile() {
_em.assertNotCloseInvoked();
_query.compile();
return this;
}
private Object execute() {
if (!isNative() && _query.getOperation() != QueryOperations.OP_SELECT)
throw new InvalidStateException(_loc.get("not-select-query", getQueryString()), null, null, false);
try {
lock();
Map params = getParameterValues();
boolean registered = preExecute(params);
Object result = _query.execute(params);
if (registered) {
postExecute(result);
}
return result;
} catch (LockTimeoutException e) {
throw new QueryTimeoutException(e.getMessage(), new Throwable[]{e}, this);
} finally {
unlock();
}
}
@Override
public List getResultList() {
_em.assertNotCloseInvoked();
boolean queryFetchPlanUsed = pushQueryFetchPlan();
try {
Object ob = execute();
if (ob instanceof List) {
List ret = (List) ob;
if (ret instanceof ResultList) {
RuntimeExceptionTranslator trans = PersistenceExceptions.getRollbackTranslator(_em);
if (_query.isDistinct()) {
return new DistinctResultList((ResultList) ret, trans);
} else {
return new DelegatingResultList((ResultList) ret, trans);
}
} else {
return ret;
}
}
return Collections.singletonList(ob);
} finally {
popQueryFetchPlan(queryFetchPlanUsed);
}
}
/**
* Execute a query that returns a single result.
*/
@Override
public X getSingleResult() {
_em.assertNotCloseInvoked();
setHint(QueryHints.HINT_RESULT_COUNT, 1); // for DB2 optimization
boolean queryFetchPlanUsed = pushQueryFetchPlan();
try {
List result = getResultList();
if (result == null || result.isEmpty())
throw new NoResultException(_loc.get("no-result", getQueryString())
.getMessage());
if (result.size() > 1)
throw new NonUniqueResultException(_loc.get("non-unique-result",
getQueryString(), result.size()).getMessage());
try {
return (X)result.get(0);
} catch (Exception e) {
throw new NoResultException(_loc.get("no-result", getQueryString())
.getMessage());
}
} finally {
popQueryFetchPlan(queryFetchPlanUsed);
}
}
private boolean pushQueryFetchPlan() {
boolean fcPushed = false;
if (_hintHandler != null) {
FetchConfiguration fc = _fetch == null ? null : ((FetchPlanImpl)_fetch).getDelegate();
_em.pushFetchPlan(fc);
return true;
}
if (_fetch != null && _hintHandler != null) {
switch (_fetch.getReadLockMode()) {
case PESSIMISTIC_READ:
case PESSIMISTIC_WRITE:
case PESSIMISTIC_FORCE_INCREMENT:
// push query fetch plan to em if pessisimistic lock and any
// hints are set
_em.pushFetchPlan(((FetchPlanImpl)_fetch).getDelegate());
fcPushed = true;
}
}
return fcPushed;
}
private void popQueryFetchPlan(boolean queryFetchPlanUsed) {
if (queryFetchPlanUsed) {
_em.popFetchPlan();
}
}
@Override
public int executeUpdate() {
_em.assertNotCloseInvoked();
Map<?,?> paramValues = getParameterValues();
if (_query.getOperation() == QueryOperations.OP_DELETE) {
return asInt(paramValues.isEmpty() ? _query.deleteAll() : _query.deleteAll(paramValues));
}
if (_query.getOperation() == QueryOperations.OP_UPDATE) {
return asInt(paramValues.isEmpty() ? _query.updateAll() : _query.updateAll(paramValues));
}
throw new InvalidStateException(_loc.get("not-update-delete-query", getQueryString()), null, null, false);
}
/**
* Cast the specified long down to an int, first checking for overflow.
*/
private static int asInt(long l) {
if (l > Integer.MAX_VALUE)
return Integer.MAX_VALUE;
if (l < Integer.MIN_VALUE) // unlikely, but we might as well check
return Integer.MIN_VALUE;
return (int) l;
}
@Override
public FlushModeType getFlushMode() {
return EntityManagerImpl.fromFlushBeforeQueries(_query
.getFetchConfiguration().getFlushBeforeQueries());
}
@Override
public OpenJPAQuery<X> setFlushMode(FlushModeType flushMode) {
_em.assertNotCloseInvoked();
_query.getFetchConfiguration().setFlushBeforeQueries(
EntityManagerImpl.toFlushBeforeQueries(flushMode));
return this;
}
/**
* Asserts that this query is a JPQL or Criteria Query.
*/
void assertJPQLOrCriteriaQuery() {
String language = getLanguage();
if (JPQLParser.LANG_JPQL.equals(language)
|| QueryLanguages.LANG_PREPARED_SQL.equals(language)
|| OpenJPACriteriaBuilder.LANG_CRITERIA.equals(language)) {
return;
} else {
throw new IllegalStateException(_loc.get("not-jpql-or-criteria-query").getMessage());
}
}
@Override
public OpenJPAQuery<X> closeAll() {
_query.closeAll();
return this;
}
@Override
public String[] getDataStoreActions(Map params) {
return _query.getDataStoreActions(params);
}
@Override
public LockModeType getLockMode() {
assertJPQLOrCriteriaQuery();
return getFetchPlan().getReadLockMode();
}
/**
* Sets lock mode on the given query.
* If the target query has been prepared and cached, then ignores the cached version.
* @see #ignorePreparedQuery()
*/
@Override
public TypedQuery<X> setLockMode(LockModeType lockMode) {
String language = getLanguage();
if (QueryLanguages.LANG_PREPARED_SQL.equals(language)) {
ignorePreparedQuery();
}
assertJPQLOrCriteriaQuery();
getFetchPlan().setReadLockMode(lockMode);
return this;
}
@Override
public int hashCode() {
return (_query == null) ? 0 : _query.hashCode();
}
@Override
public boolean equals(Object other) {
if (other == this)
return true;
if ((other == null) || (other.getClass() != this.getClass()))
return false;
if (_query == null)
return false;
return _query.equals(((QueryImpl) other)._query);
}
/**
* Get all the active hints and their values.
*
*/
//TODO: JPA 2.0 Hints that are not set to FetchConfiguration
@Override
public Map<String, Object> getHints() {
if (_hintHandler == null)
return Collections.emptyMap();
return _hintHandler.getHints();
}
@Override
public OpenJPAQuery<X> setHint(String key, Object value) {
_em.assertNotCloseInvoked();
if (_hintHandler == null) {
_hintHandler = new HintHandler(this);
}
_hintHandler.setHint(key, value);
return this;
}
@Override
public Set<String> getSupportedHints() {
if (_hintHandler == null) {
_hintHandler = new HintHandler(this);
}
return _hintHandler.getSupportedHints();
}
/**
* Unwraps this receiver to an instance of the given class, if possible.
*
* @exception if the given class is null, generic <code>Object.class</code> or a class
* that is not wrapped by this receiver.
*
* @since 2.0.0
*/
@Override
public <T> T unwrap(Class<T> cls) {
Object[] delegates = new Object[]{_query.getInnermostDelegate(), _query.getDelegate(), _query, this};
for (Object o : delegates) {
if (cls != null && cls != Object.class && cls.isInstance(o))
return (T)o;
}
// Set this transaction to rollback only (as per spec) here because the raised exception
// does not go through normal exception translation pathways
RuntimeException ex = new PersistenceException(_loc.get("unwrap-query-invalid", cls).toString(), null,
this, false);
if (_em.isActive())
_em.setRollbackOnly(ex);
throw ex;
}
// =======================================================================
// Prepared Query Cache related methods
// =======================================================================
/**
* Invoked before a query is executed.
* If this receiver is cached as a {@linkplain PreparedQuery prepared query}
* then re-parameterizes the given user parameters. The given map is cleared
* and re-parameterized values are filled in.
*
* @param params user supplied parameter key-values. Always supply a
* non-null map even if the user has not specified any parameter, because
* the same map will to be populated by re-parameterization.
*
* @return true if this invocation caused the query being registered in the
* cache.
*/
private boolean preExecute(Map params) {
PreparedQueryCache cache = _em.getPreparedQueryCache();
if (cache == null) {
return false;
}
FetchConfiguration fetch = _query.getFetchConfiguration();
if (fetch.getReadLockLevel() != 0) {
if (cache.get(_id) != null) {
ignorePreparedQuery();
}
return false;
}
// Determine if the query has NULL parameters. If so, then do not use a PreparedQuery from the cache
for (Object val : params.values()) {
if (val == null) {
ignorePreparedQuery();
return false;
}
}
Boolean registered = cache.register(_id, _query, fetch);
boolean alreadyCached = (registered == null);
String lang = _query.getLanguage();
QueryStatistics<String> stats = cache.getStatistics();
if (alreadyCached && LANG_PREPARED_SQL.equals(lang)) {
//This value is expected to be non-null as it was just registered
PreparedQuery pq = _em.getPreparedQuery(_id);
if (pq.isInitialized()) {
try {
Map rep = pq.reparametrize(params, _em.getBroker());
params.clear();
params.putAll(rep);
} catch (UserException ue) {
invalidatePreparedQuery();
Log log = _em.getConfiguration().getLog(OpenJPAConfiguration.LOG_RUNTIME);
if (log.isWarnEnabled())
log.warn(ue.getMessage());
return false;
}
}
stats.recordExecution(pq.getOriginalQuery());
} else {
stats.recordExecution(getQueryString());
}
return registered == Boolean.TRUE;
}
/**
* Initialize the registered Prepared Query from the given opaque object.
*
* @param result an opaque object representing execution result of a query
*
* @return true if the prepared query can be initialized.
*/
private boolean postExecute(Object result) {
PreparedQueryCache cache = _em.getPreparedQueryCache();
if (cache == null) {
return false;
}
return cache.initialize(_id, result) != null;
}
/**
* Remove this query from PreparedQueryCache.
*/
boolean invalidatePreparedQuery() {
PreparedQueryCache cache = _em.getPreparedQueryCache();
if (cache == null)
return false;
ignorePreparedQuery();
return cache.invalidate(_id);
}
/**
* Ignores this query from PreparedQueryCache by recreating the original
* query if it has been cached.
*/
void ignorePreparedQuery() {
PreparedQuery cached = _em.getPreparedQuery(_id);
if (cached == null)
return;
Broker broker = _em.getBroker();
// Critical assumption: Only JPQL queries are cached and more
// importantly, the identifier of the prepared query is the original
// JPQL String
String JPQL = JPQLParser.LANG_JPQL;
String jpql = _id;
org.apache.openjpa.kernel.Query newQuery = broker.newQuery(JPQL, jpql);
newQuery.getFetchConfiguration().copy(_query.getFetchConfiguration());
newQuery.compile();
_query = new DelegatingQuery(newQuery, _em.getExceptionTranslator());
}
// package protected
QueryImpl setId(String id) {
_id = id;
return this;
}
// ================ End of Prepared Query related methods =====================
@Override
protected void lock() {
if (_lock != null)
_lock.lock();
}
@Override
protected void unlock() {
if (_lock != null)
_lock.unlock();
}
@Override
protected void assertOpen() {
_query.assertOpen();
}
@Override
public OrderedMap<Object, Class<?>> getParamTypes() {
return _query.getOrderedParameterTypes();
}
@Override
public String toString() {
String result = _query.getQueryString();
return result != null ? result : _id;
}
}