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

org.hibernate.procedure.internal.ProcedureCallImpl Maven / Gradle / Ivy

There is a newer version: 7.0.0.Alpha1
Show newest version
/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2012, Red Hat Inc. or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Inc.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.hibernate.procedure.internal;

import java.sql.CallableStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.ParameterMode;

import org.hibernate.HibernateException;
import org.hibernate.QueryException;
import org.hibernate.cfg.NotYetImplementedException;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.jdbc.spi.ExtractedDatabaseMetaData;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.AbstractBasicQueryContractImpl;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.procedure.NoSuchParameterException;
import org.hibernate.procedure.ParameterRegistration;
import org.hibernate.procedure.ParameterStrategyException;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.procedure.ProcedureCallMemento;
import org.hibernate.procedure.ProcedureOutputs;
import org.hibernate.procedure.spi.ParameterRegistrationImplementor;
import org.hibernate.procedure.spi.ParameterStrategy;
import org.hibernate.result.spi.ResultContext;
import org.hibernate.type.Type;

import org.jboss.logging.Logger;

/**
 * Standard implementation of {@link org.hibernate.procedure.ProcedureCall}
 *
 * @author Steve Ebersole
 */
public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements ProcedureCall, ResultContext {
	private static final CoreMessageLogger LOG = Logger.getMessageLogger(
			CoreMessageLogger.class,
			ProcedureCallImpl.class.getName()
	);

	private static final NativeSQLQueryReturn[] NO_RETURNS = new NativeSQLQueryReturn[0];

	private final String procedureName;
	private final NativeSQLQueryReturn[] queryReturns;

	private ParameterStrategy parameterStrategy = ParameterStrategy.UNKNOWN;
	private List> registeredParameters = new ArrayList>();

	private Set synchronizedQuerySpaces;

	private ProcedureOutputsImpl outputs;

	/**
	 * The no-returns form.
	 *
	 * @param session The session
	 * @param procedureName The name of the procedure to call
	 */
	public ProcedureCallImpl(SessionImplementor session, String procedureName) {
		super( session );
		this.procedureName = procedureName;
		this.queryReturns = NO_RETURNS;
	}

	/**
	 * The result Class(es) return form
	 *
	 * @param session The session
	 * @param procedureName The name of the procedure to call
	 * @param resultClasses The classes making up the result
	 */
	public ProcedureCallImpl(final SessionImplementor session, String procedureName, Class... resultClasses) {
		super( session );
		this.procedureName = procedureName;

		final List collectedQueryReturns = new ArrayList();
		final Set collectedQuerySpaces = new HashSet();

		Util.resolveResultClasses(
				new Util.ResultClassesResolutionContext() {
					@Override
					public SessionFactoryImplementor getSessionFactory() {
						return session.getFactory();
					}

					@Override
					public void addQueryReturns(NativeSQLQueryReturn... queryReturns) {
						Collections.addAll( collectedQueryReturns, queryReturns );
					}

					@Override
					public void addQuerySpaces(String... spaces) {
						Collections.addAll( collectedQuerySpaces, spaces );
					}
				},
				resultClasses
		);

		this.queryReturns = collectedQueryReturns.toArray( new NativeSQLQueryReturn[ collectedQueryReturns.size() ] );
		this.synchronizedQuerySpaces = collectedQuerySpaces;
	}

	/**
	 * The result-set-mapping(s) return form
	 *
	 * @param session The session
	 * @param procedureName The name of the procedure to call
	 * @param resultSetMappings The names of the result set mappings making up the result
	 */
	public ProcedureCallImpl(final SessionImplementor session, String procedureName, String... resultSetMappings) {
		super( session );
		this.procedureName = procedureName;

		final List collectedQueryReturns = new ArrayList();
		final Set collectedQuerySpaces = new HashSet();

		Util.resolveResultSetMappings(
				new Util.ResultSetMappingResolutionContext() {
					@Override
					public SessionFactoryImplementor getSessionFactory() {
						return session.getFactory();
					}

					@Override
					public ResultSetMappingDefinition findResultSetMapping(String name) {
						return session.getFactory().getResultSetMapping( name );
					}

					@Override
					public void addQueryReturns(NativeSQLQueryReturn... queryReturns) {
						Collections.addAll( collectedQueryReturns, queryReturns );
					}

					@Override
					public void addQuerySpaces(String... spaces) {
						Collections.addAll( collectedQuerySpaces, spaces );
					}
				},
				resultSetMappings
		);

		this.queryReturns = collectedQueryReturns.toArray( new NativeSQLQueryReturn[ collectedQueryReturns.size() ] );
		this.synchronizedQuerySpaces = collectedQuerySpaces;
	}

	/**
	 * The named/stored copy constructor
	 *
	 * @param session The session
	 * @param memento The named/stored memento
	 */
	@SuppressWarnings("unchecked")
	ProcedureCallImpl(SessionImplementor session, ProcedureCallMementoImpl memento) {
		super( session );
		this.procedureName = memento.getProcedureName();

		this.queryReturns = memento.getQueryReturns();
		this.synchronizedQuerySpaces = Util.copy( memento.getSynchronizedQuerySpaces() );
		this.parameterStrategy = memento.getParameterStrategy();
		if ( parameterStrategy == ParameterStrategy.UNKNOWN ) {
			// nothing else to do in this case
			return;
		}

		final List storedRegistrations = memento.getParameterDeclarations();
		if ( storedRegistrations == null ) {
			// most likely a problem if ParameterStrategy is not UNKNOWN...
			LOG.debugf(
					"ParameterStrategy was [%s] on named copy [%s], but no parameters stored",
					parameterStrategy,
					procedureName
			);
			return;
		}

		final List> parameterRegistrations =
				CollectionHelper.arrayList( storedRegistrations.size() );

		for ( ProcedureCallMementoImpl.ParameterMemento storedRegistration : storedRegistrations ) {
			final ParameterRegistrationImplementor registration;
			if ( StringHelper.isNotEmpty( storedRegistration.getName() ) ) {
				if ( parameterStrategy != ParameterStrategy.NAMED ) {
					throw new IllegalStateException(
							"Found named stored procedure parameter associated with positional parameters"
					);
				}
				registration = new NamedParameterRegistration(
						this,
						storedRegistration.getName(),
						storedRegistration.getMode(),
						storedRegistration.getType(),
						storedRegistration.getHibernateType()
				);
			}
			else {
				if ( parameterStrategy != ParameterStrategy.POSITIONAL ) {
					throw new IllegalStateException(
							"Found named stored procedure parameter associated with positional parameters"
					);
				}
				registration = new PositionalParameterRegistration(
						this,
						storedRegistration.getPosition(),
						storedRegistration.getMode(),
						storedRegistration.getType(),
						storedRegistration.getHibernateType()
				);
			}
			parameterRegistrations.add( registration );
		}
		this.registeredParameters = parameterRegistrations;
	}

	@Override
	public SessionImplementor getSession() {
		return super.session();
	}

	public ParameterStrategy getParameterStrategy() {
		return parameterStrategy;
	}

	@Override
	public String getProcedureName() {
		return procedureName;
	}

	@Override
	public String getSql() {
		return getProcedureName();
	}

	@Override
	public NativeSQLQueryReturn[] getQueryReturns() {
		return queryReturns;
	}

	@Override
	@SuppressWarnings("unchecked")
	public  ParameterRegistration registerParameter(int position, Class type, ParameterMode mode) {
		final PositionalParameterRegistration parameterRegistration =
				new PositionalParameterRegistration( this, position, mode, type );
		registerParameter( parameterRegistration );
		return parameterRegistration;
	}

	@Override
	@SuppressWarnings("unchecked")
	public ProcedureCall registerParameter0(int position, Class type, ParameterMode mode) {
		registerParameter( position, type, mode );
		return this;
	}

	private void registerParameter(ParameterRegistrationImplementor parameter) {
		if ( StringHelper.isNotEmpty( parameter.getName() ) ) {
			prepareForNamedParameters();
		}
		else if ( parameter.getPosition() != null ) {
			prepareForPositionalParameters();
		}
		else {
			throw new IllegalArgumentException( "Given parameter did not define name or position [" + parameter + "]" );
		}
		registeredParameters.add( parameter );
	}

	private void prepareForPositionalParameters() {
		if ( parameterStrategy == ParameterStrategy.NAMED ) {
			throw new QueryException( "Cannot mix named and positional parameters" );
		}
		parameterStrategy = ParameterStrategy.POSITIONAL;
	}

	private void prepareForNamedParameters() {
		if ( parameterStrategy == ParameterStrategy.POSITIONAL ) {
			throw new QueryException( "Cannot mix named and positional parameters" );
		}
		if ( parameterStrategy == ParameterStrategy.UNKNOWN ) {
			// protect to only do this check once
			final ExtractedDatabaseMetaData databaseMetaData = getSession().getTransactionCoordinator()
					.getJdbcCoordinator()
					.getLogicalConnection()
					.getJdbcServices()
					.getExtractedMetaDataSupport();
			if ( ! databaseMetaData.supportsNamedParameters() ) {
				LOG.unsupportedNamedParameters();
			}
			parameterStrategy = ParameterStrategy.NAMED;
		}
	}

	@Override
	public ParameterRegistrationImplementor getParameterRegistration(int position) {
		if ( parameterStrategy != ParameterStrategy.POSITIONAL ) {
			throw new ParameterStrategyException(
					"Attempt to access positional parameter [" + position + "] but ProcedureCall using named parameters"
			);
		}
		for ( ParameterRegistrationImplementor parameter : registeredParameters ) {
			if ( position == parameter.getPosition() ) {
				return parameter;
			}
		}
		throw new NoSuchParameterException( "Could not locate parameter registered using that position [" + position + "]" );
	}

	@Override
	@SuppressWarnings("unchecked")
	public  ParameterRegistration registerParameter(String name, Class type, ParameterMode mode) {
		final NamedParameterRegistration parameterRegistration = new NamedParameterRegistration( this, name, mode, type );
		registerParameter( parameterRegistration );
		return parameterRegistration;
	}

	@Override
	@SuppressWarnings("unchecked")
	public ProcedureCall registerParameter0(String name, Class type, ParameterMode mode) {
		registerParameter( name, type, mode );
		return this;
	}

	@Override
	public ParameterRegistrationImplementor getParameterRegistration(String name) {
		if ( parameterStrategy != ParameterStrategy.NAMED ) {
			throw new ParameterStrategyException( "Names were not used to register parameters with this stored procedure call" );
		}
		for ( ParameterRegistrationImplementor parameter : registeredParameters ) {
			if ( name.equals( parameter.getName() ) ) {
				return parameter;
			}
		}
		throw new NoSuchParameterException( "Could not locate parameter registered under that name [" + name + "]" );
	}

	@Override
	@SuppressWarnings("unchecked")
	public List getRegisteredParameters() {
		return new ArrayList( registeredParameters );
	}

	@Override
	public ProcedureOutputs getOutputs() {
		if ( outputs == null ) {
			outputs = buildOutputs();
		}

		return outputs;
	}

	private ProcedureOutputsImpl buildOutputs() {
		// todo : going to need a very specialized Loader for this.
		// or, might be a good time to look at splitting Loader up into:
		//		1) building statement objects
		//		2) executing statement objects
		//		3) processing result sets

		// for now assume there are no resultClasses nor mappings defined..
		// 	TOTAL PROOF-OF-CONCEPT!!!!!!

		// todo : how to identify calls which should be in the form `{? = call procName...}` ??? (note leading param marker)
		// 		more than likely this will need to be a method on the native API.  I can see this as a trigger to
		//		both: (1) add the `? = ` part and also (2) register a REFCURSOR parameter for DBs (Oracle, PGSQL) that
		//		need it.

		final String call = session().getFactory().getDialect().getCallableStatementSupport().renderCallableStatement(
				procedureName,
				parameterStrategy,
				registeredParameters,
				session()
		);

		try {
			final CallableStatement statement = (CallableStatement) getSession().getTransactionCoordinator()
					.getJdbcCoordinator()
					.getStatementPreparer()
					.prepareStatement( call, true );


			// prepare parameters
			int i = 1;

			for ( ParameterRegistrationImplementor parameter : registeredParameters ) {
				parameter.prepare( statement, i );
				if ( parameter.getMode() == ParameterMode.REF_CURSOR ) {
					i++;
				}
				else {
					i += parameter.getSqlTypes().length;
				}
			}

			return new ProcedureOutputsImpl( this, statement );
		}
		catch (SQLException e) {
			throw getSession().getFactory().getSQLExceptionHelper().convert(
					e,
					"Error preparing CallableStatement",
					getProcedureName()
			);
		}
	}

	@Override
	public Type[] getReturnTypes() throws HibernateException {
		throw new NotYetImplementedException();
	}

	/**
	 * Use this form instead of {@link #getSynchronizedQuerySpaces()} when you want to make sure the
	 * underlying Set is instantiated (aka, on add)
	 *
	 * @return The spaces
	 */
	protected Set synchronizedQuerySpaces() {
		if ( synchronizedQuerySpaces == null ) {
			synchronizedQuerySpaces = new HashSet();
		}
		return synchronizedQuerySpaces;
	}

	@Override
	@SuppressWarnings("unchecked")
	public Set getSynchronizedQuerySpaces() {
		if ( synchronizedQuerySpaces == null ) {
			return Collections.emptySet();
		}
		else {
			return Collections.unmodifiableSet( synchronizedQuerySpaces );
		}
	}

	@Override
	public ProcedureCallImpl addSynchronizedQuerySpace(String querySpace) {
		synchronizedQuerySpaces().add( querySpace );
		return this;
	}

	@Override
	public ProcedureCallImpl addSynchronizedEntityName(String entityName) {
		addSynchronizedQuerySpaces( getSession().getFactory().getEntityPersister( entityName ) );
		return this;
	}

	protected void addSynchronizedQuerySpaces(EntityPersister persister) {
		synchronizedQuerySpaces().addAll( Arrays.asList( (String[]) persister.getQuerySpaces() ) );
	}

	@Override
	public ProcedureCallImpl addSynchronizedEntityClass(Class entityClass) {
		addSynchronizedQuerySpaces( getSession().getFactory().getEntityPersister( entityClass.getName() ) );
		return this;
	}

	@Override
	public QueryParameters getQueryParameters() {
		return buildQueryParametersObject();
	}

	@Override
	public QueryParameters buildQueryParametersObject() {
		final QueryParameters qp = super.buildQueryParametersObject();
		// both of these are for documentation purposes, they are actually handled directly...
		qp.setAutoDiscoverScalarTypes( true );
		qp.setCallable( true );
		return qp;
	}

	/**
	 * Collects any parameter registrations which indicate a REF_CURSOR parameter type/mode.
	 *
	 * @return The collected REF_CURSOR type parameters.
	 */
	public ParameterRegistrationImplementor[] collectRefCursorParameters() {
		final List refCursorParams = new ArrayList();
		for ( ParameterRegistrationImplementor param : registeredParameters ) {
			if ( param.getMode() == ParameterMode.REF_CURSOR ) {
				refCursorParams.add( param );
			}
		}
		return refCursorParams.toArray( new ParameterRegistrationImplementor[refCursorParams.size()] );
	}

	@Override
	public ProcedureCallMemento extractMemento(Map hints) {
		return new ProcedureCallMementoImpl(
				procedureName,
				Util.copy( queryReturns ),
				parameterStrategy,
				toParameterMementos( registeredParameters ),
				Util.copy( synchronizedQuerySpaces ),
				Util.copy( hints )
		);
	}


	private static List toParameterMementos(List> registeredParameters) {
		if ( registeredParameters == null ) {
			return null;
		}

		final List copy = CollectionHelper.arrayList( registeredParameters.size() );
		for ( ParameterRegistrationImplementor registration : registeredParameters ) {
			copy.add( ProcedureCallMementoImpl.ParameterMemento.fromRegistration( registration ) );
		}
		return copy;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy