/******************************************************************************* | |
* Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved. | |
* This program and the accompanying materials are made available under the | |
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* Contributors: | |
* Oracle - initial API and implementation from Oracle TopLink | |
* Zoltan NAGY & tware - updated support for MaxRows | |
* 11/01/2010-2.2 Guy Pelletier | |
* - 322916: getParameter on Query throws NPE | |
* 11/09/2010-2.1 Michael O'Brien | |
* - 329089: PERF: EJBQueryImpl.setParamenterInternal() move indexOf check inside non-native block | |
* 02/08/2012-2.4 Guy Pelletier | |
* - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls | |
* 08/11/2012-2.5 Guy Pelletier | |
* - 393867: Named queries do not work when using EM level Table Per Tenant Multitenancy. | |
******************************************************************************/ | |
package org.eclipse.persistence.internal.jpa; | |
import java.util.ArrayList; | |
import java.util.Calendar; | |
import java.util.Collection; | |
import java.util.Date; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import javax.persistence.FlushModeType; | |
import javax.persistence.LockModeType; | |
import javax.persistence.LockTimeoutException; | |
import javax.persistence.Parameter; | |
import javax.persistence.PersistenceException; | |
import javax.persistence.TemporalType; | |
import javax.persistence.TypedQuery; | |
import org.eclipse.persistence.exceptions.QueryException; | |
import org.eclipse.persistence.expressions.Expression; | |
import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform; | |
import org.eclipse.persistence.internal.helper.ClassConstants; | |
import org.eclipse.persistence.internal.helper.Helper; | |
import org.eclipse.persistence.internal.jpa.querydef.ParameterExpressionImpl; | |
import org.eclipse.persistence.internal.localization.ExceptionLocalization; | |
import org.eclipse.persistence.internal.queries.ContainerPolicy; | |
import org.eclipse.persistence.internal.queries.JPQLCallQueryMechanism; | |
import org.eclipse.persistence.internal.sessions.AbstractSession; | |
import org.eclipse.persistence.jpa.JpaQuery; | |
import org.eclipse.persistence.queries.Cursor; | |
import org.eclipse.persistence.queries.DataReadQuery; | |
import org.eclipse.persistence.queries.DatabaseQuery; | |
import org.eclipse.persistence.queries.JPAQueryBuilder; | |
import org.eclipse.persistence.queries.ModifyQuery; | |
import org.eclipse.persistence.queries.ObjectLevelReadQuery; | |
import org.eclipse.persistence.queries.ReadAllQuery; | |
import org.eclipse.persistence.queries.ReadObjectQuery; | |
import org.eclipse.persistence.queries.ReadQuery; | |
import org.eclipse.persistence.queries.ResultSetMappingQuery; | |
import org.eclipse.persistence.sessions.DatabaseRecord; | |
/** | |
* Concrete JPA query class. The JPA query wraps a DatabaseQuery which is | |
* executed. | |
*/ | |
public class EJBQueryImpl<X> extends QueryImpl implements JpaQuery<X> { | |
/** | |
* Base constructor for EJBQueryImpl. Initializes basic variables. | |
*/ | |
protected EJBQueryImpl(EntityManagerImpl entityManager) { | |
super(entityManager); | |
} | |
/** | |
* Create an EJBQueryImpl with a DatabaseQuery. | |
*/ | |
public EJBQueryImpl(DatabaseQuery query, EntityManagerImpl entityManager) { | |
super(query, entityManager); | |
} | |
/** | |
* Build an EJBQueryImpl based on the given jpql string. | |
*/ | |
public EJBQueryImpl(String jpql, EntityManagerImpl entityManager) { | |
this(jpql, entityManager, false); | |
} | |
/** | |
* Create an EJBQueryImpl with either a query name or an jpql string. | |
* | |
* @param isNamedQuery | |
* determines whether to treat the queryDescription as jpql or a | |
* query name. | |
*/ | |
public EJBQueryImpl(String queryDescription, EntityManagerImpl entityManager, boolean isNamedQuery) { | |
super(entityManager); | |
if (isNamedQuery) { | |
this.queryName = queryDescription; | |
} else { | |
if (databaseQuery == null) { | |
databaseQuery = buildEJBQLDatabaseQuery(queryDescription, entityManager.getActiveSessionIfExists()); | |
} | |
} | |
} | |
/** | |
* Build a DatabaseQuery from an jpql string. | |
* | |
* @param jpql | |
* @param session | |
* the session to get the descriptors for this query for. | |
* @return a DatabaseQuery representing the given jpql. | |
*/ | |
public static DatabaseQuery buildEJBQLDatabaseQuery(String jpql, AbstractSession session) { | |
return buildEJBQLDatabaseQuery(null, jpql, session, null, null, session.getDatasourcePlatform().getConversionManager().getLoader()); | |
} | |
/** | |
* Build a DatabaseQuery from an JPQL string. | |
* | |
* @param jpql | |
* the JPQL string. | |
* @param flushOnExecute | |
* flush the unit of work before executing the query. | |
* @param session | |
* the session to get the descriptors for this query for. | |
* @param hints | |
* a list of hints to be applied to the query. | |
* @return a DatabaseQuery representing the given jpql. | |
*/ | |
public static DatabaseQuery buildEJBQLDatabaseQuery(String queryName, String jpqlQuery, AbstractSession session, Enum lockMode, Map<String, Object> hints, ClassLoader classLoader) { | |
// PERF: Check if the JPQL has already been parsed. | |
// Only allow queries with default properties to be parse cached. | |
boolean isCacheable = (queryName == null) && (hints == null); | |
DatabaseQuery databaseQuery = null; | |
if (isCacheable) { | |
databaseQuery = (DatabaseQuery) session.getProject().getJPQLParseCache().get(jpqlQuery); | |
} | |
if ((databaseQuery == null) || (!databaseQuery.isPrepared())) { | |
JPAQueryBuilder queryBuilder = session.getQueryBuilder(); | |
databaseQuery = queryBuilder.buildQuery(jpqlQuery, session); | |
// If the query uses fetch joins, need to use JPA default of not | |
// filtering duplicates. | |
if (databaseQuery.isReadAllQuery()) { | |
ReadAllQuery readAllQuery = (ReadAllQuery) databaseQuery; | |
if (readAllQuery.hasJoining() && (readAllQuery.getDistinctState() == ReadAllQuery.DONT_USE_DISTINCT)) { | |
readAllQuery.setShouldFilterDuplicates(false); | |
} | |
} else if (databaseQuery.isModifyQuery()) { | |
// By default, do not batch modify queries, as row count must be returned. | |
((ModifyQuery)databaseQuery).setIsBatchExecutionSupported(false); | |
} | |
((JPQLCallQueryMechanism) databaseQuery.getQueryMechanism()).getJPQLCall().setIsParsed(true); | |
// Apply the lock mode. | |
if (lockMode != null && !lockMode.name().equals(ObjectLevelReadQuery.NONE)) { | |
if (databaseQuery.isObjectLevelReadQuery()) { | |
// If setting the lock mode returns true, we were unable to | |
// set the lock mode, throw an exception. | |
if (((ObjectLevelReadQuery) databaseQuery).setLockModeType(lockMode.name(), session)) { | |
throw new PersistenceException(ExceptionLocalization.buildMessage("ejb30-wrong-lock_called_without_version_locking-index", null)); | |
} | |
} else { | |
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("invalid_lock_query", (Object[]) null)); | |
} | |
} | |
// Apply any query hints. | |
databaseQuery = applyHints(hints, databaseQuery, classLoader, session); | |
// If a primary key query, switch to read-object to allow cache hit. | |
if (databaseQuery.isReadAllQuery() && !databaseQuery.isReportQuery() && ((ReadAllQuery)databaseQuery).shouldCheckCache()) { | |
ReadAllQuery readQuery = (ReadAllQuery)databaseQuery; | |
if ((readQuery.getContainerPolicy().getContainerClass() == ContainerPolicy.getDefaultContainerClass()) | |
&& (!readQuery.hasHierarchicalExpressions())) { | |
databaseQuery.checkDescriptor(session); | |
Expression selectionCriteria = databaseQuery.getSelectionCriteria(); | |
if ((selectionCriteria != null) | |
&& (databaseQuery.getDescriptor().getObjectBuilder().isPrimaryKeyExpression(true, selectionCriteria, session) | |
|| (databaseQuery.getDescriptor().getCachePolicy().isIndexableExpression(selectionCriteria, databaseQuery.getDescriptor(), session)))) { | |
ReadObjectQuery newQuery = new ReadObjectQuery(); | |
newQuery.copyFromQuery(databaseQuery); | |
databaseQuery = newQuery; | |
} | |
} | |
} | |
if (isCacheable) { | |
// Prepare query as hint may cause cloning (but not un-prepare | |
// as in read-only). | |
databaseQuery.checkPrepare(session, new DatabaseRecord()); | |
session.getProject().getJPQLParseCache().put(jpqlQuery, databaseQuery); | |
} | |
} | |
return databaseQuery; | |
} | |
/** | |
* Build a ReadAllQuery from a class and sql string. | |
*/ | |
public static DatabaseQuery buildSQLDatabaseQuery(Class resultClass, String sqlString, ClassLoader classLoader, AbstractSession session) { | |
return buildSQLDatabaseQuery(resultClass, sqlString, null, classLoader, session); | |
} | |
/** | |
* Build a ReadAllQuery for class and sql string. | |
* | |
* @param hints | |
* a list of hints to be applied to the query. | |
*/ | |
public static DatabaseQuery buildSQLDatabaseQuery(Class resultClass, String sqlString, Map<String, Object> hints, ClassLoader classLoader, AbstractSession session) { | |
ReadAllQuery query = new ReadAllQuery(resultClass); | |
query.setCall(((DatasourcePlatform)session.getPlatform(resultClass)).buildNativeCall(sqlString)); | |
query.setIsUserDefined(true); | |
// apply any query hints | |
return applyHints(hints, query, classLoader, session); | |
} | |
/** | |
* Build a DataReadQuery from a sql string. | |
*/ | |
public static DatabaseQuery buildSQLDatabaseQuery(String sqlString, ClassLoader classLoader, AbstractSession session) { | |
return buildSQLDatabaseQuery(sqlString, new HashMap<String, Object>(), classLoader, session); | |
} | |
/** | |
* Build a DataReadQuery from a sql string. | |
*/ | |
public static DatabaseQuery buildSQLDatabaseQuery(String sqlString, Map<String, Object> hints, ClassLoader classLoader, AbstractSession session) { | |
DataReadQuery query = new DataReadQuery(); | |
query.setResultType(DataReadQuery.AUTO); | |
query.setSQLString(sqlString); | |
query.setIsUserDefined(true); | |
// apply any query hints | |
return applyHints(hints, query, classLoader, session); | |
} | |
/** | |
* Build a ResultSetMappingQuery from a sql result set mapping name and sql | |
* string. | |
*/ | |
public static DatabaseQuery buildSQLDatabaseQuery(String sqlResultSetMappingName, String sqlString, ClassLoader classLoader, AbstractSession session) { | |
return buildSQLDatabaseQuery(sqlResultSetMappingName, sqlString, null, classLoader, session); | |
} | |
/** | |
* Build a ResultSetMappingQuery from a sql result set mapping name and sql | |
* string. | |
* | |
* @param hints | |
* a list of hints to be applied to the query. | |
*/ | |
public static DatabaseQuery buildSQLDatabaseQuery(String sqlResultSetMappingName, String sqlString, Map<String, Object> hints, ClassLoader classLoader, AbstractSession session) { | |
ResultSetMappingQuery query = new ResultSetMappingQuery(); | |
query.setSQLResultSetMappingName(sqlResultSetMappingName); | |
query.setCall(((DatasourcePlatform)session.getDatasourcePlatform()).buildNativeCall(sqlString)); | |
query.setIsUserDefined(true); | |
// apply any query hints | |
return applyHints(hints, query, classLoader, session); | |
} | |
/** | |
* Set an implementation-specific hint. If the hint name is not recognized, | |
* it is silently ignored. | |
* | |
* @param hintName | |
* @param value | |
* @return the same query instance | |
* @throws IllegalArgumentException | |
* if the second argument is not valid for the implementation | |
*/ | |
public TypedQuery<X> setHint(String hintName, Object value) { | |
try { | |
entityManager.verifyOpen(); | |
setHintInternal(hintName, value); | |
return this; | |
} catch (RuntimeException e) { | |
setRollbackOnly(); | |
throw e; | |
} | |
} | |
/** | |
* Set the lock mode type to be used for the query execution. | |
* | |
* @param lockMode | |
* @throws IllegalStateException | |
* if not a Java Persistence query language SELECT query | |
*/ | |
public EJBQueryImpl setLockMode(LockModeType lockMode) { | |
return (EJBQueryImpl) super.setLockMode(lockMode); | |
} | |
/** | |
* Non-standard method to return results of a ReadQuery that has a | |
* containerPolicy that returns objects as a collection rather than a List | |
* | |
* @return Collection of results | |
*/ | |
public Collection getResultCollection() { | |
// bug51411440: need to throw IllegalStateException if query | |
// executed on closed em | |
this.entityManager.verifyOpenWithSetRollbackOnly(); | |
setAsSQLReadQuery(); | |
propagateResultProperties(); | |
// bug:4297903, check container policy class and throw exception if its | |
// not the right type | |
DatabaseQuery query = getDatabaseQueryInternal(); | |
try { | |
if (query.isReadAllQuery()) { | |
Class containerClass = ((ReadAllQuery) getDatabaseQueryInternal()).getContainerPolicy().getContainerClass(); | |
if (!Helper.classImplementsInterface(containerClass, ClassConstants.Collection_Class)) { | |
throw QueryException.invalidContainerClass(containerClass, ClassConstants.Collection_Class); | |
} | |
} else if (query.isReadObjectQuery()) { | |
List resultList = new ArrayList(); | |
Object result = executeReadQuery(); | |
if (result != null) { | |
resultList.add(executeReadQuery()); | |
} | |
return resultList; | |
} else if (!query.isReadQuery()) { | |
throw new IllegalStateException(ExceptionLocalization.buildMessage("incorrect_query_for_get_result_collection")); | |
} | |
return (Collection) executeReadQuery(); | |
} catch (LockTimeoutException exception) { | |
throw exception; | |
} catch (PersistenceException exception) { | |
setRollbackOnly(); | |
throw exception; | |
} catch (IllegalStateException exception) { | |
setRollbackOnly(); | |
throw exception; | |
} catch (RuntimeException exception) { | |
setRollbackOnly(); | |
throw new PersistenceException(exception); | |
} | |
} | |
/** | |
* Non-standard method to return results of a ReadQuery that uses a Cursor. | |
* | |
* @return Cursor on results, either a CursoredStream, or ScrollableCursor | |
*/ | |
public Cursor getResultCursor() { | |
// bug51411440: need to throw IllegalStateException if query executed on closed em | |
this.entityManager.verifyOpenWithSetRollbackOnly(); | |
try { | |
setAsSQLReadQuery(); | |
propagateResultProperties(); | |
// bug:4297903, check container policy class and throw exception if its | |
// not the right type | |
if (getDatabaseQueryInternal() instanceof ReadAllQuery) { | |
if (!((ReadAllQuery) getDatabaseQueryInternal()).getContainerPolicy().isCursorPolicy()) { | |
Class containerClass = ((ReadAllQuery) getDatabaseQueryInternal()).getContainerPolicy().getContainerClass(); | |
throw QueryException.invalidContainerClass(containerClass, Cursor.class); | |
} | |
} else if (getDatabaseQueryInternal() instanceof ReadObjectQuery) { | |
// bug:4300879, no support for ReadObjectQuery if a collection is required | |
throw QueryException.incorrectQueryObjectFound(getDatabaseQueryInternal(), ReadAllQuery.class); | |
} else if (!(getDatabaseQueryInternal() instanceof ReadQuery)) { | |
throw new IllegalStateException(ExceptionLocalization.buildMessage("incorrect_query_for_get_result_collection")); | |
} | |
Object result = executeReadQuery(); | |
return (Cursor) result; | |
} catch (LockTimeoutException e) { | |
throw e; | |
} catch (PersistenceException exception) { | |
setRollbackOnly(); | |
throw exception; | |
} catch (IllegalStateException exception) { | |
setRollbackOnly(); | |
throw exception; | |
} catch (RuntimeException exception) { | |
setRollbackOnly(); | |
throw new PersistenceException(exception); | |
} | |
} | |
/** | |
* Execute a query that returns a single result. | |
* | |
* @return the result | |
* @throws javax.persistence.EntityNotFoundException | |
* if there is no result | |
* @throws javax.persistence.NonUniqueResultException | |
* if more than one result | |
*/ | |
@Override | |
public X getSingleResult() { | |
return (X) super.getSingleResult(); | |
} | |
/** | |
* Set the position of the first result to retrieve. | |
* | |
* @param start | |
* position of the first result, numbered from 0 | |
* @return the same query instance | |
*/ | |
public EJBQueryImpl setFirstResult(int startPosition) { | |
return (EJBQueryImpl) super.setFirstResult(startPosition); | |
} | |
/** | |
* Set the flush mode type to be used for the query execution. | |
* | |
* @param flushMode | |
*/ | |
public EJBQueryImpl setFlushMode(FlushModeType flushMode) { | |
return (EJBQueryImpl) super.setFlushMode(flushMode); | |
} | |
/** | |
* Set the maximum number of results to retrieve. | |
* | |
* @param maxResult | |
* @return the same query instance | |
*/ | |
public EJBQueryImpl setMaxResults(int maxResult) { | |
return (EJBQueryImpl) super.setMaxResults(maxResult); | |
} | |
/** | |
* Bind an instance of java.util.Calendar to a positional parameter. | |
* | |
* @param position | |
* @param value | |
* @param temporalType | |
* @return the same query instance | |
*/ | |
public TypedQuery setParameter(int position, Calendar value, TemporalType temporalType) { | |
entityManager.verifyOpenWithSetRollbackOnly(); | |
return setParameter(position, convertTemporalType(value, temporalType)); | |
} | |
/** | |
* Bind an instance of java.util.Date to a positional parameter. | |
* | |
* @param position | |
* @param value | |
* @param temporalType | |
* @return the same query instance | |
*/ | |
public TypedQuery setParameter(int position, Date value, TemporalType temporalType) { | |
entityManager.verifyOpenWithSetRollbackOnly(); | |
return setParameter(position, convertTemporalType(value, temporalType)); | |
} | |
/** | |
* Bind an argument to a positional parameter. | |
* | |
* @param position | |
* @param value | |
* @return the same query instance | |
*/ | |
public TypedQuery setParameter(int position, Object value) { | |
try { | |
entityManager.verifyOpen(); | |
setParameterInternal(position, value); | |
return this; | |
} catch (RuntimeException e) { | |
setRollbackOnly(); | |
throw e; | |
} | |
} | |
/** | |
* Bind an instance of java.util.Calendar to a Parameter object. | |
* | |
* @param parameter | |
* @param value | |
* @param temporalType | |
* @return the same query instance | |
* @throws IllegalArgumentException | |
* if position does not correspond to a parameter of the query | |
*/ | |
public TypedQuery setParameter(Parameter<Calendar> param, Calendar value, TemporalType temporalType) { | |
entityManager.verifyOpenWithSetRollbackOnly(); | |
if (param == null) | |
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NULL_PARAMETER_PASSED_TO_SET_PARAMETER")); | |
//bug 402686: type validation | |
String position = getParameterId(param); | |
ParameterExpressionImpl parameter = (ParameterExpressionImpl) this.getInternalParameters().get(position); | |
if (parameter == null ) { | |
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NO_PARAMETER_WITH_NAME", new Object[] { param.toString(), this.databaseQuery })); | |
} | |
if (!parameter.getParameterType().equals(param.getParameterType())) { | |
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("INCORRECT_PARAMETER_TYPE", new Object[] { position, param.getParameterType() })); | |
} | |
return this.setParameter(position, value, temporalType); | |
} | |
/** | |
* Bind an instance of java.util.Date to a Parameter object. | |
* | |
* @param parameter | |
* object | |
* @param value | |
* @param temporalType | |
* @return the same query instance | |
* @throws IllegalArgumentException | |
* if position does not correspond to a parameter of the query | |
*/ | |
public TypedQuery setParameter(Parameter<Date> param, Date value, TemporalType temporalType) { | |
if (param == null) | |
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NULL_PARAMETER_PASSED_TO_SET_PARAMETER")); | |
//bug 402686: type validation | |
String position = getParameterId(param); | |
ParameterExpressionImpl parameter = (ParameterExpressionImpl) this.getInternalParameters().get(position); | |
if (parameter == null ) { | |
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NO_PARAMETER_WITH_NAME", new Object[] { param.toString(), this.databaseQuery })); | |
} | |
if (!parameter.getParameterType().equals(param.getParameterType())) { | |
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("INCORRECT_PARAMETER_TYPE", new Object[] { position, param.getParameterType() })); | |
} | |
return this.setParameter(position, value, temporalType); | |
} | |
/** | |
* Set the value of a Parameter object. | |
* | |
* @param param | |
* parameter to be set | |
* @param value | |
* parameter value | |
* @return query instance | |
* @throws IllegalArgumentException | |
* if parameter does not correspond to a parameter of the query | |
*/ | |
public <T> TypedQuery setParameter(Parameter<T> param, T value) { | |
if (param == null) { | |
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NULL_PARAMETER_PASSED_TO_SET_PARAMETER")); | |
} | |
//bug 402686: type validation | |
String position = getParameterId(param); | |
ParameterExpressionImpl parameter = (ParameterExpressionImpl) this.getInternalParameters().get(position); | |
if (parameter == null ) { | |
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("NO_PARAMETER_WITH_NAME", new Object[] { param.toString(), this.databaseQuery })); | |
} | |
if (!parameter.getParameterType().equals(param.getParameterType())) { | |
throw new IllegalArgumentException(ExceptionLocalization.buildMessage("INCORRECT_PARAMETER_TYPE", new Object[] { position, param.getParameterType() })); | |
} | |
return this.setParameter(position, value); | |
} | |
/** | |
* Bind an instance of java.util.Calendar to a named parameter. | |
* | |
* @param name | |
* @param value | |
* @param temporalType | |
* @return the same query instance | |
*/ | |
public TypedQuery setParameter(String name, Calendar value, TemporalType temporalType) { | |
entityManager.verifyOpenWithSetRollbackOnly(); | |
return setParameter(name, convertTemporalType(value, temporalType)); | |
} | |
/** | |
* Bind an instance of java.util.Date to a named parameter. | |
* | |
* @param name | |
* @param value | |
* @param temporalType | |
* @return the same query instance | |
*/ | |
public TypedQuery setParameter(String name, Date value, TemporalType temporalType) { | |
entityManager.verifyOpenWithSetRollbackOnly(); | |
return setParameter(name, convertTemporalType(value, temporalType)); | |
} | |
/** | |
* Bind an argument to a named parameter. | |
* | |
* @param name | |
* the parameter name | |
* @param value | |
* @return the same query instance | |
*/ | |
public TypedQuery setParameter(String name, Object value) { | |
try { | |
entityManager.verifyOpen(); | |
setParameterInternal(name, value, false); | |
return this; | |
} catch (RuntimeException e) { | |
setRollbackOnly(); | |
throw e; | |
} | |
} | |
public String toString() { | |
return getClass().getSimpleName() + "(" + String.valueOf(this.databaseQuery) + ")"; | |
} | |
} |