/******************************************************************************* * Copyright (c) 1998, 2015 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 * arnaud nauwynck, tware - Bug 274975 - ensure custom sql calls are only translated once ******************************************************************************/ package org.eclipse.persistence.queries; import java.util.List; import java.io.*; import org.eclipse.persistence.internal.databaseaccess.*; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.sessions.AbstractRecord; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.exceptions.ValidationException; import org.eclipse.persistence.internal.expressions.ParameterExpression; import org.eclipse.persistence.mappings.structures.ObjectRelationalDatabaseField; /** * <b>Purpose</b>: Used as an abstraction of an SQL call. * A call is an SQL string with parameters. */ public class SQLCall extends DatabaseCall implements QueryStringCall { protected boolean hasCustomSQLArguments; transient protected boolean isTranslatedCustomQuery; /** * PUBLIC: * Create a new SQL call. */ public SQLCall() { super(); this.hasCustomSQLArguments = false; this.isTranslatedCustomQuery = false; } /** * PUBLIC: * Create a new SQL call. * Warning: Allowing an unverified SQL string to be passed into this * method makes your application vulnerable to SQL injection attacks. */ public SQLCall(String sqlString) { this(); setSQLString(sqlString); } /** * INTERNAL: * Set the data passed through setCustomSQLArgumentType and useCustomSQLCursorOutputAsResultSet methods. */ protected void afterTranslateCustomQuery(List updatedParameters, List<Integer> updatedParameterTypes) { int size = getParameters().size(); for (int i = 0; i < size; i++) { Integer parameterType = this.parameterTypes.get(i); Object parameter = this.parameters.get(i); if ((parameterType == MODIFY) || (parameterType == OUT) || (parameterType == OUT_CURSOR) || ((parameterType == IN) && parameter instanceof DatabaseField)) { DatabaseField field = afterTranslateCustomQueryUpdateParameter((DatabaseField)parameter, i, parameterType, updatedParameters, updatedParameterTypes); if (field!=null){ this.parameters.set(i, field); } } else if (parameterType == INOUT) { DatabaseField outField = afterTranslateCustomQueryUpdateParameter((DatabaseField)((Object[])parameter)[1], i, parameterType, updatedParameters, updatedParameterTypes); if (outField != null) { if (((Object[])parameter)[0] instanceof DatabaseField){ if ( ((Object[])parameter)[0] != ((Object[])parameter)[1] ) { DatabaseField inField = outField.clone(); inField.setName( ((DatabaseField)((Object[])parameter)[0]).getName()); ((Object[])parameter)[0] = inField; } else { ((Object[])parameter)[0] = outField; } } ((Object[])parameter)[1] = outField; } } else if ((parameterType == IN) && (parameter instanceof DatabaseField)){ DatabaseField field = afterTranslateCustomQueryUpdateParameter((DatabaseField)parameter, i, parameterType, updatedParameters, updatedParameterTypes); if (field != null) { this.parameters.set(i, field); } } } } /** * INTERNAL: * Set the data passed through setCustomSQLArgumentType and useCustomSQLCursorOutputAsResultSet methods. * This will return the null if the user did not add the field/type using the setCustomSQLArgumentType method */ protected DatabaseField afterTranslateCustomQueryUpdateParameter(DatabaseField field, int index, Integer parameterType, List updatedParameters, List<Integer> updatedParameterTypes) { int size = updatedParameters.size(); for (int j = 0; j < size; j++) { DatabaseField updateField = (DatabaseField)updatedParameters.get(j); if (field.equals(updateField)) { Integer updateParameterType = updatedParameterTypes.get(j); if (updateParameterType == null) { return updateField; } else if (updateParameterType == OUT_CURSOR) { if (parameterType == OUT) { this.parameterTypes.set(index, OUT_CURSOR); return updateField; } else { throw ValidationException.cannotSetCursorForParameterTypeOtherThanOut(field.getName(), toString()); } } break; } } return null; } /** * INTERNAL: * Used to avoid misinterpreting the # in custom SQL. */ public boolean hasCustomSQLArguments() { return hasCustomSQLArguments; } @Override public boolean isSQLCall() { return true; } @Override public boolean isQueryStringCall() { return true; } /** * INTERNAL: * Called by prepare method only. */ @Override protected void prepareInternal(AbstractSession session) { if (hasCustomSQLArguments()) { // hold results of setCustomSQLArgumentType and useCustomSQLCursorOutputAsResultSet methods List updatedParameters = null; List updatedParameterTypes = null; if (getParameters().size() > 0) { updatedParameters = getParameters(); setParameters(org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance()); updatedParameterTypes = getParameterTypes(); setParameterTypes(org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance()); } translateCustomQuery(); if (updatedParameters != null) { afterTranslateCustomQuery(updatedParameters, updatedParameterTypes); } } super.prepareInternal(session); } /** * INTERNAL: * Used to avoid misinterpreting the # in custom SQL. */ public void setHasCustomSQLArguments(boolean hasCustomSQLArguments) { this.hasCustomSQLArguments = hasCustomSQLArguments; } /** * PUBLIC: * This method should only be used with custom SQL: * it sets a type to IN, OUT or INOUT parameter (prefixed with #, ### or #### in custom SQL string). */ public void setCustomSQLArgumentType(String customParameterName, Class type) { DatabaseField field = new DatabaseField(customParameterName); field.setType(type); getParameters().add(field); getParameterTypes().add(null); } /** * PUBLIC: * This method should only be used with custom SQL: * it sets a type to IN, OUT or INOUT parameter (prefixed with #, ### or #### in custom SQL string). * The argumentFieldName is the field or argument name used in the SQL. * The type is the JDBC type code for the parameter. */ public void setCustomSQLArgumentType(String argumentFieldName, int type) { DatabaseField field = new DatabaseField(argumentFieldName); field.setSqlType(type); getParameters().add(field); getParameterTypes().add(null); } /** * PUBLIC: * This method should only be used with custom SQL: * it sets a type to IN, OUT or INOUT parameter (prefixed with #, ### or #### in custom SQL string). * The argumentFieldName is the field or argument name used in the SQL. * The type is the JDBC type code for the parameter. * The typeName is the JDBC type name, this may be required for ARRAY or STRUCT types. */ public void setCustomSQLArgumentType(String argumentFieldName, int type, String typeName) { ObjectRelationalDatabaseField field = new ObjectRelationalDatabaseField(argumentFieldName); field.setSqlType(type); field.setSqlTypeName(typeName); getParameters().add(field); getParameterTypes().add(null); } /** * PUBLIC: * This method should only be used with custom SQL: * it sets a type to IN, OUT or INOUT parameter (prefixed with #, ### or #### in custom SQL string). * TThe argumentFieldName is the field or argument name used in the SQL. * The type is the JDBC type code for the parameter. * The typeName is the JDBC type name, this may be required for ARRAY or STRUCT types. * The javaType is the java class to return instead of the ARRAY and STRUCT types if a conversion is possible. */ public void setCustomSQLArgumentType(String argumentFieldName, int type, String typeName, Class javaType) { ObjectRelationalDatabaseField field = new ObjectRelationalDatabaseField(argumentFieldName); field.setSqlType(type); field.setSqlTypeName(typeName); field.setType(javaType); getParameters().add(field); getParameterTypes().add(null); } /** * PUBLIC: * This method should only be used with custom SQL: * it sets a type to IN, OUT or INOUT parameter (prefixed with #, ### or #### in custom SQL string). * TThe argumentFieldName is the field or argument name used in the SQL. * The type is the JDBC type code for the parameter. * The typeName is the JDBC type name, this may be required for ARRAY or STRUCT types. * The nestedType is a DatabaseField with type information set to match the VARRAYs object types */ public void setCustomSQLArgumentType(String argumentFieldName, int type, String typeName, DatabaseField nestedType) { ObjectRelationalDatabaseField field = new ObjectRelationalDatabaseField(argumentFieldName); field.setSqlType(type); field.setSqlTypeName(typeName); field.setNestedTypeField(nestedType); getParameters().add(field); getParameterTypes().add(null); } /** * PUBLIC: * This method should only be used with custom SQL: * it sets a type to IN, OUT or INOUT parameter (prefixed with #, ### or #### in custom SQL string). * TThe argumentFieldName is the field or argument name used in the SQL. * The type is the JDBC type code for the parameter. * The typeName is the JDBC type name, this may be required for ARRAY or STRUCT types. * The javaType is the java class to return instead of the ARRAY and STRUCT types if a conversion is possible. * The nestedType is a DatabaseField with type information set to match the VARRAYs object types */ public void setCustomSQLArgumentType(String argumentFieldName, int type, String typeName, Class javaType, DatabaseField nestedType) { ObjectRelationalDatabaseField field = new ObjectRelationalDatabaseField(argumentFieldName); field.setSqlType(type); field.setSqlTypeName(typeName); field.setType(javaType); field.setNestedTypeField(nestedType); getParameters().add(field); getParameterTypes().add(null); } /** * Set the SQL string. * Warning: Allowing an unverified SQL string to be passed into this * method makes your application vulnerable to SQL injection attacks. */ public void setSQLString(String sqlString) { setSQLStringInternal(sqlString); } /** * INTERNAL: * Keep track of the fact that this call has been translated. This information is used to * ensure the translation code for a custom SQLCall is only run once * In the case of inheritance we will try to call the translation code once to get the * list of types and again for each subclass */ @Override public void translateCustomQuery() { super.translateCustomQuery(); isTranslatedCustomQuery = true; } /** * INTERNAL: * Only translate the call if it was not previously translated * * This code ensures the translation code for a custom SQLCall is only run once * In the case of inheritance we will try to call the translation code once to get the * list of types and again for each subclass */ @Override public void translatePureSQLCustomQuery() { if (isTranslatedCustomQuery) { return; } super.translatePureSQLCustomQuery(); isTranslatedCustomQuery = true; } /** * INTERNAL: * All values are printed as ? to allow for parameter binding or translation during the execute of the call. */ public void appendTranslationParameter(Writer writer, ParameterExpression expression, DatabasePlatform platform, AbstractRecord record) throws IOException { try { platform.writeParameterMarker(writer, expression, record, this); } catch (IOException exception) { throw ValidationException.fileError(exception); } getParameters().add(expression); getParameterTypes().add(TRANSLATION); } /** * PUBLIC: * This method should only be used with custom SQL: * Used for Oracle result sets through procedures. * It defines OUT parameter (prefixed with ### in custom SQL string) * as a cursor output. */ public void useCustomSQLCursorOutputAsResultSet(String customParameterName) { DatabaseField field = new DatabaseField(customParameterName); getParameters().add(field); getParameterTypes().add(OUT_CURSOR); setIsCursorOutputProcedure(true); } }