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;
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<ParameterRegistrationImplementor<?>> registeredParameters = new ArrayList<ParameterRegistrationImplementor<?>>();
private Set<String> synchronizedQuerySpaces;
private ProcedureOutputsImpl outputs;
public ProcedureCallImpl(SessionImplementor session, String procedureName) {
super( session );
this.procedureName = procedureName;
this.queryReturns = NO_RETURNS;
}
public ProcedureCallImpl(final SessionImplementor session, String procedureName, Class... resultClasses) {
super( session );
this.procedureName = procedureName;
final List<NativeSQLQueryReturn> collectedQueryReturns = new ArrayList<NativeSQLQueryReturn>();
final Set<String> collectedQuerySpaces = new HashSet<String>();
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;
}
public ProcedureCallImpl(final SessionImplementor session, String procedureName, String... resultSetMappings) {
super( session );
this.procedureName = procedureName;
final List<NativeSQLQueryReturn> collectedQueryReturns = new ArrayList<NativeSQLQueryReturn>();
final Set<String> collectedQuerySpaces = new HashSet<String>();
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;
}
@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 ) {
return;
}
final List<ProcedureCallMementoImpl.ParameterMemento> storedRegistrations = memento.getParameterDeclarations();
if ( storedRegistrations == null ) {
LOG.debugf(
"ParameterStrategy was [%s] on named copy [%s], but no parameters stored",
parameterStrategy,
procedureName
);
return;
}
final List<ParameterRegistrationImplementor<?>> 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 <T> ParameterRegistration<T> registerParameter(int position, Class<T> 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 ) {
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 <T> ParameterRegistration<T> registerParameter(String name, Class<T> 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<ParameterRegistration> getRegisteredParameters() {
return new ArrayList<ParameterRegistration>( registeredParameters );
}
@Override
public ProcedureOutputs getOutputs() {
if ( outputs == null ) {
outputs = buildOutputs();
}
return outputs;
}
private ProcedureOutputsImpl buildOutputs() {
final String call = session().getFactory().getDialect().getCallableStatementSupport().renderCallableStatement(
procedureName,
parameterStrategy,
registeredParameters,
session()
);
try {
final CallableStatement statement = (CallableStatement) getSession().getTransactionCoordinator()
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( call, true );
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();
}
protected Set<String> synchronizedQuerySpaces() {
if ( synchronizedQuerySpaces == null ) {
synchronizedQuerySpaces = new HashSet<String>();
}
return synchronizedQuerySpaces;
}
@Override
@SuppressWarnings("unchecked")
public Set<String> 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();
qp.setAutoDiscoverScalarTypes( true );
qp.setCallable( true );
return qp;
}
public ParameterRegistrationImplementor[] collectRefCursorParameters() {
final List<ParameterRegistrationImplementor> refCursorParams = new ArrayList<ParameterRegistrationImplementor>();
for ( ParameterRegistrationImplementor param : registeredParameters ) {
if ( param.getMode() == ParameterMode.REF_CURSOR ) {
refCursorParams.add( param );
}
}
return refCursorParams.toArray( new ParameterRegistrationImplementor[refCursorParams.size()] );
}
@Override
public ProcedureCallMemento (Map<String, Object> hints) {
return new ProcedureCallMementoImpl(
procedureName,
Util.copy( queryReturns ),
parameterStrategy,
toParameterMementos( registeredParameters ),
Util.copy( synchronizedQuerySpaces ),
Util.copy( hints )
);
}
private static List<ProcedureCallMementoImpl.ParameterMemento> toParameterMementos(List<ParameterRegistrationImplementor<?>> registeredParameters) {
if ( registeredParameters == null ) {
return null;
}
final List<ProcedureCallMementoImpl.ParameterMemento> copy = CollectionHelper.arrayList( registeredParameters.size() );
for ( ParameterRegistrationImplementor registration : registeredParameters ) {
copy.add( ProcedureCallMementoImpl.ParameterMemento.fromRegistration( registration ) );
}
return copy;
}
}