All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.skyway.spring.util.dao.call.AdvancedCallMetaDataContext Maven / Gradle / Ivy

The newest version!
/**
* Copyright 2007 - 2011 Skyway Software, Inc.
*/
package org.skyway.spring.util.dao.call;

import java.sql.DatabaseMetaData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.sql.DataSource;

import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.SqlReturnResultSet;
import org.springframework.jdbc.core.metadata.CallMetaDataContext;
import org.springframework.jdbc.core.metadata.CallMetaDataProvider;
import org.springframework.jdbc.core.metadata.CallParameterMetaData;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
import org.springframework.jdbc.support.JdbcUtils;

@SuppressWarnings("nls")
public class AdvancedCallMetaDataContext extends CallMetaDataContext {
	/** List of SqlParameter objects to be used in call execution */
	private List callParameters = new ArrayList();
	
	/** the provider of call meta data */
	private CallMetaDataProvider metaDataProvider;
	
	/** Set of in parameter names to exclude use for any not listed */
	private Set limitedInParameterNames = new HashSet();
	
	/**
	 * Initialize this class with metadata from the database 
	 * @param dataSource the DataSource used to retrieve metadata
	 */
	@Override
	public void initializeMetaData(DataSource dataSource) {
		this.metaDataProvider = VendorTypeHandlerFactory.createMetaDataProvider(dataSource, this);
	}

	public CallMetaDataProvider getMetaDataProvider() {
		return metaDataProvider;
	}

	public void setMetaDataProvider(CallMetaDataProvider metaDataProvider) {
		this.metaDataProvider = metaDataProvider;
	}

	/**
	 * Create a ReturnResultSetParameter/SqlOutParameter depending on the support provided
	 * by the JDBC driver used for the database in use.
	 * @param parameterName the name of the parameter (also used as the name of the List returned in the output)
	 * @param rowMapper a RowMapper implementation used to map the data retuned in the result set
	 * @return the appropriate SqlParameter
	 */
	@Override
	public SqlParameter createReturnResultSetParameter(String parameterName, RowMapper rowMapper) {
		if (this.metaDataProvider.isReturnResultSetSupported()) {
			return new SqlReturnResultSet(parameterName, rowMapper);
		}
		else {
			if (this.metaDataProvider.isRefCursorSupported()) {
				return new SqlOutParameter(parameterName, this.metaDataProvider.getRefCursorSqlType(), rowMapper);
			}
			else {
				throw new InvalidDataAccessApiUsageException("Return of a ResultSet from a stored procedure is not supported.");
			}
		}
	}

	/**
	 * Process the list of parameters provided and if procedure column metedata is used the
	 * parameters will be matched against the metadata information and any missing ones will
	 * be automatically included
	 * @param parameters the list of parameters to use as a base
	 */
	@Override
	public void processParameters(List parameters) {
		this.callParameters = reconcileParameters(parameters);
	}

	/**
	 * Reconcile the provided parameters with available metadata and add new ones where appropriate
	 */
	private List reconcileParameters(List parameters) {
		final List declaredReturnParameters = new ArrayList();
		final Map declaredParameters = new LinkedHashMap();
		boolean returnDeclared = false;
		List outParameterNames = new ArrayList();

		// Separate implicit return parameters from explicit parameters...
		for (SqlParameter parameter : parameters) {
			if (parameter.isResultsParameter()) {
				declaredReturnParameters.add(parameter);
			}
			else {
				String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameter.getName()).toLowerCase();
				declaredParameters.put(parameterNameToMatch, parameter);
				if (parameter instanceof SqlOutParameter) {
					outParameterNames.add(parameter.getName());
					if (this.isFunction()) {
						if (!returnDeclared)
							this.setFunctionReturnName(parameter.getName());
						returnDeclared = true;
					}
				}
			}
		}
		this.setOutParameterNames(outParameterNames);

		final List workParameters = new ArrayList();
		workParameters.addAll(declaredReturnParameters);

		if (!this.metaDataProvider.isProcedureColumnMetaDataUsed()) {
			workParameters.addAll(declaredParameters.values());
			return workParameters;
		}

		Map limitedInParamNamesMap = new HashMap(this.limitedInParameterNames.size());
		for (String limitedParameterName : this.limitedInParameterNames) {
			limitedInParamNamesMap.put(
					this.metaDataProvider.parameterNameToUse(limitedParameterName).toLowerCase(), limitedParameterName);
		}

		for (CallParameterMetaData meta : metaDataProvider.getCallParameterMetaData()) {
			String parNameToCheck = null;
			if (meta.getParameterName() != null) {
				parNameToCheck = this.metaDataProvider.parameterNameToUse(meta.getParameterName()).toLowerCase();
			}
			String parNameToUse = this.metaDataProvider.parameterNameToUse(meta.getParameterName());
			if (declaredParameters.containsKey(parNameToCheck) ||
					(meta.getParameterType() == DatabaseMetaData.procedureColumnReturn && returnDeclared)) {
				SqlParameter parameter;
				if (meta.getParameterType() == DatabaseMetaData.procedureColumnReturn) {
					parameter = declaredParameters.get(this.getFunctionReturnName());
					if (parameter == null && this.getOutParameterNames().size() > 0) {
						parameter = declaredParameters.get(this.getOutParameterNames().get(0).toLowerCase());
					}
					if (parameter == null) {
						throw new InvalidDataAccessApiUsageException(
								"Unable to locate declared parameter for function return value - " +
										" add an SqlOutParameter with name \"" + getFunctionReturnName() +"\"");
					}
					else {
						this.setFunctionReturnName(parameter.getName());
					}
				}
				else {
					parameter = declaredParameters.get(parNameToCheck);
				}
				if (parameter != null) {
					workParameters.add(parameter);
				}
			}
			else {
				if (meta.getParameterType() == DatabaseMetaData.procedureColumnReturn) {
					if (!isFunction() && !isReturnValueRequired() &&
							this.metaDataProvider.byPassReturnParameter(meta.getParameterName())) {
					}
					else {
						String returnNameToUse =
								( meta.getParameterName() == null || meta.getParameterName().length() < 1 ) ? 
										this.getFunctionReturnName() : parNameToUse;
						workParameters.add(new SqlOutParameter(returnNameToUse, meta.getSqlType()));
						if (this.isFunction())
							outParameterNames.add(returnNameToUse);
					}
				}
				else {
					if (meta.getParameterType() == DatabaseMetaData.procedureColumnOut) {
						workParameters.add(this.metaDataProvider.createDefaultOutParameter(parNameToUse, meta));
						outParameterNames.add(parNameToUse);
					}
					else if (meta.getParameterType() == DatabaseMetaData.procedureColumnInOut) {
						workParameters.add(this.metaDataProvider.createDefaultInOutParameter(parNameToUse, meta));
						outParameterNames.add(parNameToUse);
					}
					else {
						if (this.limitedInParameterNames.size() == 0 ||
								limitedInParamNamesMap.containsKey(parNameToUse.toLowerCase())) {
							workParameters.add(this.metaDataProvider.createDefaultInParameter(parNameToUse, meta));
						}
					}
				}
			}
		}

		return workParameters;

	}

	/**
	 * Match input parameter values with the parameters declared to be used in the call.
	 * @param parameterSource the input values
	 * @return a Map containing the matched parameter names with the value taken from the input
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Map matchInParameterValuesWithCallParameters(SqlParameterSource parameterSource) {
		// For parameter source lookups we need to provide case-insensitive lookup support
		// since the database metadata is not necessarily providing case sensitive parameter names.
		Map caseInsensitiveParameterNames =
				SqlParameterSourceUtils.extractCaseInsensitiveParameterNames(parameterSource);

		Map callParameterNames = new HashMap(this.callParameters.size());
		Map matchedParameters = new HashMap(this.callParameters.size());
		for (SqlParameter parameter : this.callParameters) {
			if (parameter.isInputValueProvided()) {
				String parameterName = parameter.getName();
				String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
				if (parameterNameToMatch != null) {
					callParameterNames.put(parameterNameToMatch.toLowerCase(), parameterName);
				}
				if (parameterName != null) {
					if (parameterSource.hasValue(parameterName)) {
						matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, parameterName));
					}
					else {
						String lowerCaseName = parameterName.toLowerCase();
						if (parameterSource.hasValue(lowerCaseName)) {
							matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, lowerCaseName));
						}
						else {
							String propertyName = JdbcUtils.convertUnderscoreNameToPropertyName(parameterName);
							if (parameterSource.hasValue(propertyName)) {
								matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, propertyName));
							}
							else {
								if (caseInsensitiveParameterNames.containsKey(lowerCaseName)) {
									String sourceName = (String) caseInsensitiveParameterNames.get(lowerCaseName);
									matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, sourceName));
								}
							}
						}
					}
				}
			}
		}

		return matchedParameters;
	}

	/**
	 * Match input parameter values with the parameters declared to be used in the call.
	 * @param inParameters the input values
	 * @return a Map containing the matched parameter names with the value taken from the input
	 */
	@Override
	public Map matchInParameterValuesWithCallParameters(Map inParameters) {
		if (!this.metaDataProvider.isProcedureColumnMetaDataUsed()) {
			return inParameters;
		}
		Map callParameterNames = new HashMap(this.callParameters.size());
		for (SqlParameter parameter : this.callParameters) {
			if (parameter.isInputValueProvided()) {
				String parameterName =  parameter.getName();
				String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
				if (parameterNameToMatch != null) {
					callParameterNames.put(parameterNameToMatch.toLowerCase(), parameterName);
				}
			}
		}
		Map matchedParameters = new HashMap(inParameters.size());
		for (String parameterName : inParameters.keySet()) {
			String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
			String callParameterName = callParameterNames.get(parameterNameToMatch.toLowerCase());
			if (callParameterName != null) {
				matchedParameters.put(callParameterName, inParameters.get(parameterName));
			}
		}
		if (matchedParameters.size() < callParameterNames.size()) {
			for (String parameterName : callParameterNames.keySet()) {
				String parameterNameToMatch = this.metaDataProvider.parameterNameToUse(parameterName);
				@SuppressWarnings("unused")
				String callParameterName = callParameterNames.get(parameterNameToMatch.toLowerCase());
			}
		}

		return matchedParameters;
	}

	/**
	 * Get the List of SqlParameter objects to be used in call execution
	 */
	@Override
	public List getCallParameters() {
		return this.callParameters;
	}

	/**
	 * Build the call string based on configuration and metadata information.
	 * @return the call string to be used
	 */
	@Override
	public String createCallString() {
		String callString;
		int parameterCount = 0;
		String catalogNameToUse = null;
		String schemaNameToUse = null;

		// For Oracle where catalogs are not supported we need to reverse the schema name
		// and the catalog name since the cataog is used for the package name
		if (this.metaDataProvider.isSupportsSchemasInProcedureCalls() &&
				!this.metaDataProvider.isSupportsCatalogsInProcedureCalls()) {
			schemaNameToUse = this.metaDataProvider.catalogNameToUse(this.getCatalogName());
			catalogNameToUse = this.metaDataProvider.schemaNameToUse(this.getSchemaName());
		}
		else {
			catalogNameToUse = this.metaDataProvider.catalogNameToUse(this.getCatalogName());
			schemaNameToUse = this.metaDataProvider.schemaNameToUse(this.getSchemaName());
		}
		String procedureNameToUse = this.metaDataProvider.procedureNameToUse(this.getProcedureName());
		if (this.isFunction() || this.isReturnValueRequired()) {
			callString = "{? = call " +
					(catalogNameToUse != null && catalogNameToUse.length() > 0 ? catalogNameToUse + "." : "") +
					(schemaNameToUse != null && schemaNameToUse.length() > 0 ? schemaNameToUse + "." : "") +
					procedureNameToUse + "(";
			parameterCount = -1;
		}
		else {
			callString = "{call " +
					(catalogNameToUse != null && catalogNameToUse.length() > 0 ? catalogNameToUse + "." : "") +
					(schemaNameToUse != null && schemaNameToUse.length() > 0 ? schemaNameToUse + "." : "") +
					procedureNameToUse + "(";
		}
		for (SqlParameter parameter : this.callParameters) {
			if (!(parameter.isResultsParameter())) {
				if (parameterCount > 0) {
					callString += ", ";
				}
				if (parameterCount >= 0) {
					callString += "?";
				}
				parameterCount++;
			}
		}
		callString += ")}";

		return callString;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy