blob: d813ccdb1f0db81020c13bf7d32a606d4d822771 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 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 v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// 08/01/2012-2.5 Chris Delahunt
// - 371950: Metadata caching
// 08/24/2012-2.5 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 org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.OptimisticLockException;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import jakarta.persistence.LockModeType;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.queries.EntityResult;
import org.eclipse.persistence.queries.SQLResultSetMapping;
import org.eclipse.persistence.queries.StoredProcedureCall;
import org.eclipse.persistence.sessions.Session;
/**
* <b>Purpose</b>:
* A JPA placeholder Query object to store JPQL strings so that processing the string is delayed
* until Login.
*
* @author Chris Delahunt
* @since TopLink Essentials
*/
public class JPAQuery extends DatabaseQuery {
private String lockMode;
private String jpqlString;
private String sqlString;
private StoredProcedureCall call;
private String resultClassName;
private List<String> resultClassNames;
private List<String> resultSetMappingNames;
private Map<String, Object> hints;
public JPAQuery() {
}
public JPAQuery(String jpqlString) {
this.jpqlString=jpqlString;
}
/**
* JPQL
*/
public JPAQuery(String name, String jpqlString, String lockMode, Map<String, Object> hints) {
this.name = name;
this.jpqlString = jpqlString;
this.flushOnExecute = null;
this.hints = hints;
this.lockMode = lockMode;
if (lockMode == null) {
this.lockMode = "NONE";
}
}
/*
* SQL returning an entity
*/
public JPAQuery(String queryName, String sqlString, Map<String, Object> hints) {
this.name = queryName;
this.sqlString = sqlString;
this.flushOnExecute = null;
this.hints = hints;
this.lockMode = null;
}
/*
* Stored Proc returning an Entity
*/
public JPAQuery(String queryName, StoredProcedureCall call, Map<String, Object> hints) {
this.name = queryName;
this.call = call;
this.flushOnExecute = null;
this.hints = hints;
this.lockMode = null;
}
public void addResultClassNames(String className) {
if (resultClassNames == null) {
resultClassNames = new ArrayList<>();
}
this.resultClassNames.add(className);
}
public void addResultSetMapping(String resultSetMapping){
if (resultSetMappingNames == null) {
resultSetMappingNames = new ArrayList<>();
}
this.resultSetMappingNames.add(resultSetMapping);
}
/**
* INTERNAL:
* This should never be called and is only here because it is needed as an extension
* to DatabaseQuery. Perhaps exception should be thrown to warn users, but for now
* it will execute the resulting query instead, this allows JPA style queries to be executed
* on a normal EclipseLink Session.
*/
@Override
public Object executeDatabaseQuery() throws DatabaseException, OptimisticLockException{
return getSession().executeQuery(getDatabaseQuery());
}
public DatabaseQuery getDatabaseQuery() {
return (DatabaseQuery)getProperty("databasequery");
}
/**
* INTERNAL:
* For table per tenant queries the descriptor list will extracted from
* parsing the jpql query and cached here.
*/
@Override
public List<ClassDescriptor> getDescriptors() {
return descriptors;
}
/**
* Return the JPA query hints.
*/
public Map<String, Object> getHints(){
return hints;
}
/**
* Return the JPQL string.
*/
@Override
public String getJPQLString(){
return jpqlString;
}
/**
* Return true if this query is a jpql query.
*/
public boolean isJPQLQuery() {
return jpqlString != null;
}
/**
* Return true if this query is an sql query.
*/
public boolean isSQLQuery() {
return sqlString != null;
}
/**
* INTERNAL:
* Generate the DatabaseQuery query from the JPA named query.
*/
@Override
public void prepare() {
DatabaseQuery query = null;
ClassLoader loader = session.getDatasourcePlatform().getConversionManager().getLoader();
if (isSQLQuery()) {
query = processSQLQuery(getSession());
} else if (isJPQLQuery()) {
query = processJPQLQuery(getSession());
} else if (call != null) {
query = processStoredProcedureQuery(getSession());
if (call.hasParameters() ) {
//convert the type in the parameters; query.convertClassNamesToClasses does not cascade to the call
for (Object value: call.getParameters()) {
if (value instanceof Object[]) {
//must be inout type, and the out portion is a DatabaseField
((DatabaseField) ((Object[])value)[1]).convertClassNamesToClasses(loader);
value = ((Object[])value)[0];
}
if (value instanceof DatabaseField) {
((DatabaseField) value).convertClassNamesToClasses(loader);
}
}
}
}
// Make sure all class names have been converted.
query.convertClassNamesToClasses(loader);
setDatabaseQuery(query);
}
/**
* INTERNAL:
* Convert the JPA query into a DatabaseQuery.
*/
public DatabaseQuery processJPQLQuery(Session session){
ClassLoader classloader = session.getDatasourcePlatform().getConversionManager().getLoader();
LockModeType lockModeEnum = null;
// Must handle errors if a JPA 2.0 option is used in JPA 1.0.
try {
lockModeEnum = LockModeType.valueOf(lockMode);
} catch (Exception ignore) {
// Ignore JPA 2.0 in JPA 1.0, reverts to no lock.
}
DatabaseQuery ejbquery = EJBQueryImpl.buildEJBQLDatabaseQuery(getName(), jpqlString, (AbstractSession) session, lockModeEnum, hints, classloader);
ejbquery.setName(getName());
return ejbquery;
}
/**
* INTERNAL:
* Convert the SQL string into a DatabaseQuery.
*/
public DatabaseQuery processSQLQuery(Session session){
DatabaseQuery query = null;
ClassLoader loader = session.getDatasourcePlatform().getConversionManager().getLoader();
if (resultClassName != null) {
Class<?> clazz = session.getDatasourcePlatform().getConversionManager().convertClassNameToClass(resultClassName);
query = EJBQueryImpl.buildSQLDatabaseQuery(clazz, sqlString, hints, loader, (AbstractSession)session);
} else if (resultSetMappingNames != null) {
query = EJBQueryImpl.buildSQLDatabaseQuery(resultSetMappingNames.get(0), sqlString, hints, loader, (AbstractSession)session);
} else {
// Neither a resultClass or resultSetMapping is specified so place in a temp query on the session
query = EJBQueryImpl.buildSQLDatabaseQuery(sqlString, hints, loader, (AbstractSession)session);
}
query.setName(this.getName());
return query;
}
/**
* INTERNAL:
* Convert the StoredProc call into a DatabaseQuery.
*/
public DatabaseQuery processStoredProcedureQuery(Session session){
DatabaseQuery query = null;
ClassLoader loader = session.getDatasourcePlatform().getConversionManager().getLoader();
if (resultClassNames != null) {
List<SQLResultSetMapping> resultSetMappings = new ArrayList<>();
for (String resultClass : resultClassNames) {
SQLResultSetMapping mapping = new SQLResultSetMapping(resultClass);
EntityResult entityResult = new EntityResult(resultClass);
mapping.addResult(entityResult);
resultSetMappings.add(mapping);
}
query = StoredProcedureQueryImpl.buildResultSetMappingQuery(resultSetMappings, call, hints, loader, (AbstractSession)session);
} else if (resultSetMappingNames != null) {
query = StoredProcedureQueryImpl.buildResultSetMappingNameQuery(resultSetMappingNames, call, hints, loader, (AbstractSession)session);
} else if (resultClassName != null) {
Class<?> clazz = session.getDatasourcePlatform().getConversionManager().convertClassNameToClass(resultClassName);
query = StoredProcedureQueryImpl.buildStoredProcedureQuery(clazz, call, hints, loader, (AbstractSession)session);
} else {
// Neither a resultClass or resultSetMapping is specified so place in a temp query on the session.
if (call.isStoredFunctionCall() || call.isStoredPLSQLProcedureCall()) {
// If it is a function (plsql or not) or plsql procedure use the data read query.
query = StoredProcedureQueryImpl.buildStoredProcedureQuery(call, hints, loader, (AbstractSession)session);
} else {
// Otherwise use a result set mapping query for stored procedure calls so users can use the execute
// method on it (JPA 2.1 API). Will return the same result, that is, Object[] in this case.
query = StoredProcedureQueryImpl.buildResultSetMappingQuery(new ArrayList<>(), call, hints, loader, (AbstractSession)session);
}
}
query.setName(getName());
return query;
}
public void setDatabaseQuery(DatabaseQuery databaseQuery) {
setProperty("databasequery", databaseQuery);
}
/**
* INTERNAL:
* For table per tenant queries the descriptor list will extracted from
* parsing the jpql query and cached here.
*/
public void setDescriptors(List<ClassDescriptor> descriptors) {
this.descriptors = descriptors;
}
public void setHints(Map<String, Object> hints){
this.hints = hints;
}
@Override
public void setJPQLString(String jpqlString){
this.jpqlString = jpqlString;
}
public void setResultClassName(String className){
this.resultClassName = className;
}
public void setResultSetMappings(List<String> resultSetMappings){
this.resultSetMappingNames = resultSetMappings;
}
}