/******************************************************************************* * Copyright (c) 1998, 2016 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 jpqlQuery * the JPQL string. * @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 */ @Override 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 */ @Override 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 */ @Override 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 */ @Override 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 startPosition * position of the first result, numbered from 0 * @return the same query instance */ @Override public EJBQueryImpl setFirstResult(int startPosition) { return (EJBQueryImpl) super.setFirstResult(startPosition); } /** * Set the flush mode type to be used for the query execution. * * @param flushMode */ @Override 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 */ @Override 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 */ @Override 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 */ @Override 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 */ @Override 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 param * @param value * @param temporalType * @return the same query instance * @throws IllegalArgumentException * if position does not correspond to a parameter of the query */ @Override 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 param * object * @param value * @param temporalType * @return the same query instance * @throws IllegalArgumentException * if position does not correspond to a parameter of the query */ @Override 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 */ @Override 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 */ @Override 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 */ @Override 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 */ @Override public TypedQuery setParameter(String name, Object value) { try { entityManager.verifyOpen(); setParameterInternal(name, value, false); return this; } catch (RuntimeException e) { setRollbackOnly(); throw e; } } @Override public String toString() { return getClass().getSimpleName() + "(" + String.valueOf(this.databaseQuery) + ")"; } }