From 8c95a6077a523c47482fbae14ab54b763fa73a23 Mon Sep 17 00:00:00 2001 From: Steve Ebersole Date: Thu, 9 May 2013 14:47:58 -0500 Subject: [PATCH] HHH-8222 - Implement @NamedStoredProcedureQuery binding --- .../org/hibernate/SharedSessionContract.java | 13 +- .../org/hibernate/cfg/AnnotationBinder.java | 42 +++ .../java/org/hibernate/cfg/Configuration.java | 18 +- .../main/java/org/hibernate/cfg/Mappings.java | 10 + .../NamedProcedureCallDefinition.java | 237 ++++++++++++++++ .../cfg/annotations/QueryBinder.java | 15 ++ .../engine/spi/SessionDelegatorBaseImpl.java | 83 ++++++ .../engine/spi/SessionFactoryImplementor.java | 1 + .../internal/AbstractSessionImpl.java | 36 ++- .../internal/NamedQueryRepository.java | 29 +- .../internal/SessionFactoryImpl.java | 21 +- .../util/collections/CollectionHelper.java | 12 + .../custom/sql/SQLQueryReturnProcessor.java | 31 ++- .../hibernate/procedure/ProcedureCall.java | 17 ++ .../procedure/ProcedureCallMemento.java | 63 +++++ .../AbstractParameterRegistrationImpl.java | 72 ++++- .../internal/NamedParameterRegistration.java | 19 +- .../ParameterRegistrationImplementor.java | 30 +++ .../procedure/internal/ParameterStrategy.java | 9 + .../PositionalParameterRegistration.java | 19 +- .../procedure/internal/ProcedureCallImpl.java | 253 ++++++++++++++---- .../internal/ProcedureCallMementoImpl.java | 169 ++++++++++++ .../hibernate/procedure/internal/Util.java | 116 ++++++++ .../internal/EntityManagerFactoryImpl.java | 27 +- .../internal/StoredProcedureQueryImpl.java | 4 + .../jpa/spi/AbstractEntityManagerImpl.java | 40 ++- 26 files changed, 1275 insertions(+), 111 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/cfg/annotations/NamedProcedureCallDefinition.java create mode 100644 hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCallMemento.java create mode 100644 hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java create mode 100644 hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index 33f76243cf..d847c28a4a 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -26,6 +26,7 @@ package org.hibernate; import java.io.Serializable; import org.hibernate.procedure.ProcedureCall; +import org.hibernate.procedure.ProcedureCallMemento; /** * Contract methods shared between {@link Session} and {@link StatelessSession}. @@ -86,6 +87,17 @@ public interface SharedSessionContract extends Serializable { */ public SQLQuery createSQLQuery(String queryString); + /** + * Gets a ProcedureCall based on a named template + * + * @param name The name given to the template + * + * @return The ProcedureCall + * + * @see javax.persistence.NamedStoredProcedureQuery + */ + public ProcedureCall getNamedProcedureCall(String name); + /** * Creates a call to a stored procedure. * @@ -154,5 +166,4 @@ public interface SharedSessionContract extends Serializable { * @return The criteria instance for manipulation and execution */ public Criteria createCriteria(String entityName, String alias); - } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java index 0414122b4f..77034482f6 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AnnotationBinder.java @@ -68,6 +68,8 @@ import javax.persistence.NamedNativeQueries; import javax.persistence.NamedNativeQuery; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; +import javax.persistence.NamedStoredProcedureQueries; +import javax.persistence.NamedStoredProcedureQuery; import javax.persistence.OneToMany; import javax.persistence.OneToOne; import javax.persistence.OrderColumn; @@ -253,6 +255,28 @@ public final class AnnotationBinder { } } } + + { + final List annotations = + (List) defaults.get( NamedStoredProcedureQuery.class ); + if ( annotations != null ) { + for ( NamedStoredProcedureQuery annotation : annotations ) { + QueryBinder.bindNamedStoredProcedureQuery( annotation, mappings ); + } + } + } + + { + final List annotations = + (List) defaults.get( NamedStoredProcedureQueries.class ); + if ( annotations != null ) { + for ( NamedStoredProcedureQueries annotation : annotations ) { + for ( NamedStoredProcedureQuery queryAnnotation : annotation.value() ) { + QueryBinder.bindNamedStoredProcedureQuery( queryAnnotation, mappings ); + } + } + } + } } public static void bindPackage(String packageName, Mappings mappings) { @@ -358,6 +382,24 @@ public final class AnnotationBinder { ); QueryBinder.bindNativeQueries( ann, mappings ); } + + // NamedStoredProcedureQuery handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + { + final NamedStoredProcedureQuery annotation = annotatedElement.getAnnotation( NamedStoredProcedureQuery.class ); + if ( annotation != null ) { + QueryBinder.bindNamedStoredProcedureQuery( annotation, mappings ); + } + } + + // NamedStoredProcedureQueries handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + { + final NamedStoredProcedureQueries annotation = annotatedElement.getAnnotation( NamedStoredProcedureQueries.class ); + if ( annotation != null ) { + for ( NamedStoredProcedureQuery queryAnnotation : annotation.value() ) { + QueryBinder.bindNamedStoredProcedureQuery( queryAnnotation, mappings ); + } + } + } } private static IdGenerator buildIdGenerator(java.lang.annotation.Annotation ann, Mappings mappings) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java index cc6139a56a..ea3f4b8a2b 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Configuration.java @@ -81,6 +81,7 @@ import org.hibernate.annotations.common.reflection.java.JavaReflectionManager; import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; +import org.hibernate.cfg.annotations.NamedProcedureCallDefinition; import org.hibernate.cfg.annotations.reflection.JPAMetadataProvider; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; import org.hibernate.dialect.Dialect; @@ -214,6 +215,7 @@ public class Configuration implements Serializable { protected Map namedQueries; protected Map namedSqlQueries; + protected Map namedProcedureCallMap; protected Map sqlResultSetMappings; protected Map typeDefs; @@ -1772,6 +1774,10 @@ public class Configuration implements Serializable { return namedQueries; } + public Map getNamedProcedureCallMap() { + return namedProcedureCallMap; + } + /** * Create a {@link SessionFactory} using the properties and mappings in this configuration. The * {@link SessionFactory} will be immutable, so changes made to {@code this} {@link Configuration} after @@ -2764,7 +2770,7 @@ public class Configuration implements Serializable { public Table getTable(String schema, String catalog, String name) { String key = Table.qualify(catalog, schema, name); - return tables.get(key); + return tables.get( key ); } public Iterator iterateTables() { @@ -2870,6 +2876,16 @@ public class Configuration implements Serializable { namedSqlQueries.put( name.intern(), query ); } + @Override + public void addNamedProcedureCallDefinition(NamedProcedureCallDefinition definition) + throws DuplicateMappingException { + final String name = definition.getRegisteredName(); + final NamedProcedureCallDefinition previous = namedProcedureCallMap.put( name, definition ); + if ( previous != null ) { + throw new DuplicateMappingException( "named stored procedure query", name ); + } + } + public void addDefaultSQLQuery(String name, NamedSQLQueryDefinition query) { applySQLQuery( name, query ); defaultNamedNativeQueryNames.add( name ); diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java b/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java index a0d6543fc6..4b49851563 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Mappings.java @@ -37,6 +37,7 @@ import org.hibernate.MappingException; import org.hibernate.annotations.AnyMetaDef; import org.hibernate.annotations.common.reflection.ReflectionManager; import org.hibernate.annotations.common.reflection.XClass; +import org.hibernate.cfg.annotations.NamedProcedureCallDefinition; import org.hibernate.engine.ResultSetMappingDefinition; import org.hibernate.engine.spi.FilterDefinition; import org.hibernate.engine.spi.NamedQueryDefinition; @@ -338,6 +339,15 @@ public interface Mappings { */ public void addSQLQuery(String name, NamedSQLQueryDefinition query) throws DuplicateMappingException; + /** + * Adds metadata for a named stored procedure call to this repository. + * + * @param definition The procedure call information + * + * @throws DuplicateMappingException If a query already exists with that name. + */ + public void addNamedProcedureCallDefinition(NamedProcedureCallDefinition definition) throws DuplicateMappingException; + /** * Get the metadata for a named SQL result set mapping. * diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/NamedProcedureCallDefinition.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/NamedProcedureCallDefinition.java new file mode 100644 index 0000000000..b50173c016 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/NamedProcedureCallDefinition.java @@ -0,0 +1,237 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.cfg.annotations; + +import javax.persistence.NamedStoredProcedureQuery; +import javax.persistence.ParameterMode; +import javax.persistence.QueryHint; +import javax.persistence.StoredProcedureParameter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.hibernate.LockMode; +import org.hibernate.MappingException; +import org.hibernate.engine.ResultSetMappingDefinition; +import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; +import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.SessionFactoryImpl; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.procedure.ProcedureCallMemento; +import org.hibernate.procedure.internal.ParameterStrategy; +import org.hibernate.procedure.internal.ProcedureCallMementoImpl; +import org.hibernate.procedure.internal.Util; + +import static org.hibernate.procedure.internal.ProcedureCallMementoImpl.ParameterMemento; + +/** + * Holds all the information needed from a named procedure call declaration in order to create a + * {@link org.hibernate.procedure.internal.ProcedureCallImpl} + * + * @author Steve Ebersole + * + * @see javax.persistence.NamedStoredProcedureQuery + */ +public class NamedProcedureCallDefinition { + private final String registeredName; + private final String procedureName; + private final Class[] resultClasses; + private final String[] resultSetMappings; + private final ParameterDefinitions parameterDefinitions; + private final Map hints; + + NamedProcedureCallDefinition(NamedStoredProcedureQuery annotation) { + this.registeredName = annotation.name(); + this.procedureName = annotation.procedureName(); + this.resultClasses = annotation.resultClasses(); + this.resultSetMappings = annotation.resultSetMappings(); + this.parameterDefinitions = new ParameterDefinitions( annotation.parameters() ); + this.hints = extract( annotation.hints() ); + + final boolean specifiesResultClasses = resultClasses != null && resultClasses.length > 0; + final boolean specifiesResultSetMappings = resultSetMappings != null && resultSetMappings.length > 0; + + if ( specifiesResultClasses && specifiesResultSetMappings ) { + throw new MappingException( + String.format( + "NamedStoredProcedureQuery [%s] specified both resultClasses and resultSetMappings", + registeredName + ) + ); + } + } + + private Map extract(QueryHint[] hints) { + if ( hints == null || hints.length == 0 ) { + return Collections.emptyMap(); + } + final Map hintsMap = new HashMap(); + for ( QueryHint hint : hints ) { + hintsMap.put( hint.name(), hint.value() ); + } + return hintsMap; + } + + public String getRegisteredName() { + return registeredName; + } + + public String getProcedureName() { + return procedureName; + } + + public ProcedureCallMemento toMemento( + final SessionFactoryImpl sessionFactory, + final Map resultSetMappingDefinitions) { + final List collectedQueryReturns = new ArrayList(); + final Set collectedQuerySpaces = new HashSet(); + + final boolean specifiesResultClasses = resultClasses != null && resultClasses.length > 0; + final boolean specifiesResultSetMappings = resultSetMappings != null && resultSetMappings.length > 0; + + if ( specifiesResultClasses ) { + Util.resolveResultClasses( + new Util.ResultClassesResolutionContext() { + @Override + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + @Override + public void addQueryReturns(NativeSQLQueryReturn... queryReturns) { + Collections.addAll( collectedQueryReturns, queryReturns ); + } + + @Override + public void addQuerySpaces(String... spaces) { + Collections.addAll( collectedQuerySpaces, spaces ); + } + }, + resultClasses + ); + } + else if ( specifiesResultSetMappings ) { + Util.resolveResultSetMappings( + new Util.ResultSetMappingResolutionContext() { + @Override + public SessionFactoryImplementor getSessionFactory() { + return sessionFactory; + } + + @Override + public ResultSetMappingDefinition findResultSetMapping(String name) { + return resultSetMappingDefinitions.get( name ); + } + + @Override + public void addQueryReturns(NativeSQLQueryReturn... queryReturns) { + Collections.addAll( collectedQueryReturns, queryReturns ); + } + + @Override + public void addQuerySpaces(String... spaces) { + Collections.addAll( collectedQuerySpaces, spaces ); + } + }, + resultSetMappings + ); + } + + return new ProcedureCallMementoImpl( + procedureName, + collectedQueryReturns.toArray( new NativeSQLQueryReturn[ collectedQueryReturns.size() ] ), + parameterDefinitions.getParameterStrategy(), + parameterDefinitions.toMementos( sessionFactory ), + collectedQuerySpaces, + hints + ); + } + + static class ParameterDefinitions { + private final ParameterStrategy parameterStrategy; + private final ParameterDefinition[] parameterDefinitions; + + ParameterDefinitions(StoredProcedureParameter[] parameters) { + if ( parameters == null || parameters.length == 0 ) { + parameterStrategy = ParameterStrategy.POSITIONAL; + parameterDefinitions = new ParameterDefinition[0]; + } + else { + parameterStrategy = StringHelper.isNotEmpty( parameters[0].name() ) + ? ParameterStrategy.NAMED + : ParameterStrategy.POSITIONAL; + parameterDefinitions = new ParameterDefinition[ parameters.length ]; + for ( int i = 0; i < parameters.length; i++ ) { + parameterDefinitions[i] = new ParameterDefinition( i, parameters[i] ); + } + } + } + + public ParameterStrategy getParameterStrategy() { + return parameterStrategy; + } + + public List toMementos(SessionFactoryImpl sessionFactory) { + final List mementos = new ArrayList(); + for ( ParameterDefinition definition : parameterDefinitions ) { + definition.toMemento( sessionFactory ); + } + return mementos; + } + } + + static class ParameterDefinition { + private final Integer position; + private final String name; + private final ParameterMode parameterMode; + private final Class type; + + ParameterDefinition(int position, StoredProcedureParameter annotation) { + this.position = position; + this.name = normalize( annotation.name() ); + this.parameterMode = annotation.mode(); + this.type = annotation.type(); + } + + public ParameterMemento toMemento(SessionFactoryImpl sessionFactory) { + return new ParameterMemento( + position, + name, + parameterMode, + type, + sessionFactory.getTypeResolver().heuristicType( type.getName() ) + ); + } + } + + private static String normalize(String name) { + return StringHelper.isNotEmpty( name ) ? name : null; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java index 1299636ba5..2c21637b04 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java @@ -28,6 +28,7 @@ import javax.persistence.NamedNativeQueries; import javax.persistence.NamedNativeQuery; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; +import javax.persistence.NamedStoredProcedureQuery; import javax.persistence.QueryHint; import javax.persistence.SqlResultSetMapping; import javax.persistence.SqlResultSetMappings; @@ -331,6 +332,20 @@ public abstract class QueryBinder { } } + public static void bindNamedStoredProcedureQuery(NamedStoredProcedureQuery annotation, Mappings mappings) { + if ( annotation == null ) { + return; + } + + if ( BinderHelper.isEmptyAnnotationValue( annotation.name() ) ) { + throw new AnnotationException( "A named query must have a name when used in class or package level" ); + } + + final NamedProcedureCallDefinition def = new NamedProcedureCallDefinition( annotation ); + mappings.addNamedProcedureCallDefinition( def ); + LOG.debugf( "Bound named stored procedure query : %s => %s", def.getRegisteredName(), def.getProcedureName() ); + } + public static void bindSqlResultsetMappings(SqlResultSetMappings ann, Mappings mappings, boolean isDefault) { if ( ann == null ) return; for (SqlResultSetMapping rs : ann.value()) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java index 9e2686574c..d9c1908465 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java @@ -380,22 +380,31 @@ public class SessionDelegatorBaseImpl implements SessionImplementor, Session { // Delegates to Session + @Override public Transaction beginTransaction() { return session.beginTransaction(); } + @Override public Transaction getTransaction() { return session.getTransaction(); } + @Override public Query createQuery(String queryString) { return session.createQuery( queryString ); } + @Override public SQLQuery createSQLQuery(String queryString) { return session.createSQLQuery( queryString ); } + @Override + public ProcedureCall getNamedProcedureCall(String name) { + return session.getNamedProcedureCall( name ); + } + @Override public ProcedureCall createStoredProcedureCall(String procedureName) { return session.createStoredProcedureCall( procedureName ); @@ -411,298 +420,372 @@ public class SessionDelegatorBaseImpl implements SessionImplementor, Session { return session.createStoredProcedureCall( procedureName, resultSetMappings ); } + @Override public Criteria createCriteria(Class persistentClass) { return session.createCriteria( persistentClass ); } + @Override public Criteria createCriteria(Class persistentClass, String alias) { return session.createCriteria( persistentClass, alias ); } + @Override public Criteria createCriteria(String entityName) { return session.createCriteria( entityName ); } + @Override public Criteria createCriteria(String entityName, String alias) { return session.createCriteria( entityName, alias ); } + @Override public SharedSessionBuilder sessionWithOptions() { return session.sessionWithOptions(); } + @Override public SessionFactory getSessionFactory() { return session.getSessionFactory(); } + @Override public Connection close() throws HibernateException { return session.close(); } + @Override public void cancelQuery() throws HibernateException { session.cancelQuery(); } + @Override public boolean isDirty() throws HibernateException { return session.isDirty(); } + @Override public boolean isDefaultReadOnly() { return session.isDefaultReadOnly(); } + @Override public void setDefaultReadOnly(boolean readOnly) { session.setDefaultReadOnly( readOnly ); } + @Override public Serializable getIdentifier(Object object) { return session.getIdentifier( object ); } + @Override public boolean contains(Object object) { return session.contains( object ); } + @Override public void evict(Object object) { session.evict( object ); } + @Override public Object load(Class theClass, Serializable id, LockMode lockMode) { return session.load( theClass, id, lockMode ); } + @Override public Object load(Class theClass, Serializable id, LockOptions lockOptions) { return session.load( theClass, id, lockOptions ); } + @Override public Object load(String entityName, Serializable id, LockMode lockMode) { return session.load( entityName, id, lockMode ); } + @Override public Object load(String entityName, Serializable id, LockOptions lockOptions) { return session.load( entityName, id, lockOptions ); } + @Override public Object load(Class theClass, Serializable id) { return session.load( theClass, id ); } + @Override public Object load(String entityName, Serializable id) { return session.load( entityName, id ); } + @Override public void load(Object object, Serializable id) { session.load( object, id ); } + @Override public void replicate(Object object, ReplicationMode replicationMode) { session.replicate( object, replicationMode ); } + @Override public void replicate(String entityName, Object object, ReplicationMode replicationMode) { session.replicate( entityName, object, replicationMode ); } + @Override public Serializable save(Object object) { return session.save( object ); } + @Override public Serializable save(String entityName, Object object) { return session.save( entityName, object ); } + @Override public void saveOrUpdate(Object object) { session.saveOrUpdate( object ); } + @Override public void saveOrUpdate(String entityName, Object object) { session.saveOrUpdate( entityName, object ); } + @Override public void update(Object object) { session.update( object ); } + @Override public void update(String entityName, Object object) { session.update( entityName, object ); } + @Override public Object merge(Object object) { return session.merge( object ); } + @Override public Object merge(String entityName, Object object) { return session.merge( entityName, object ); } + @Override public void persist(Object object) { session.persist( object ); } + @Override public void persist(String entityName, Object object) { session.persist( entityName, object ); } + @Override public void delete(Object object) { session.delete( object ); } + @Override public void delete(String entityName, Object object) { session.delete( entityName, object ); } + @Override public void lock(Object object, LockMode lockMode) { session.lock( object, lockMode ); } + @Override public void lock(String entityName, Object object, LockMode lockMode) { session.lock( entityName, object, lockMode ); } + @Override public LockRequest buildLockRequest(LockOptions lockOptions) { return session.buildLockRequest( lockOptions ); } + @Override public void refresh(Object object) { session.refresh( object ); } + @Override public void refresh(String entityName, Object object) { session.refresh( entityName, object ); } + @Override public void refresh(Object object, LockMode lockMode) { session.refresh( object, lockMode ); } + @Override public void refresh(Object object, LockOptions lockOptions) { session.refresh( object, lockOptions ); } + @Override public void refresh(String entityName, Object object, LockOptions lockOptions) { session.refresh( entityName, object, lockOptions ); } + @Override public LockMode getCurrentLockMode(Object object) { return session.getCurrentLockMode( object ); } + @Override public Query createFilter(Object collection, String queryString) { return session.createFilter( collection, queryString ); } + @Override public void clear() { session.clear(); } + @Override public Object get(Class clazz, Serializable id) { return session.get( clazz, id ); } + @Override public Object get(Class clazz, Serializable id, LockMode lockMode) { return session.get( clazz, id, lockMode ); } + @Override public Object get(Class clazz, Serializable id, LockOptions lockOptions) { return session.get( clazz, id, lockOptions ); } + @Override public Object get(String entityName, Serializable id) { return session.get( entityName, id ); } + @Override public Object get(String entityName, Serializable id, LockMode lockMode) { return session.get( entityName, id, lockMode ); } + @Override public Object get(String entityName, Serializable id, LockOptions lockOptions) { return session.get( entityName, id, lockOptions ); } + @Override public String getEntityName(Object object) { return session.getEntityName( object ); } + @Override public IdentifierLoadAccess byId(String entityName) { return session.byId( entityName ); } + @Override public IdentifierLoadAccess byId(Class entityClass) { return session.byId( entityClass ); } + @Override public NaturalIdLoadAccess byNaturalId(String entityName) { return session.byNaturalId( entityName ); } + @Override public NaturalIdLoadAccess byNaturalId(Class entityClass) { return session.byNaturalId( entityClass ); } + @Override public SimpleNaturalIdLoadAccess bySimpleNaturalId(String entityName) { return session.bySimpleNaturalId( entityName ); } + @Override public SimpleNaturalIdLoadAccess bySimpleNaturalId(Class entityClass) { return session.bySimpleNaturalId( entityClass ); } + @Override public Filter enableFilter(String filterName) { return session.enableFilter( filterName ); } + @Override public Filter getEnabledFilter(String filterName) { return session.getEnabledFilter( filterName ); } + @Override public void disableFilter(String filterName) { session.disableFilter( filterName ); } + @Override public SessionStatistics getStatistics() { return session.getStatistics(); } + @Override public boolean isReadOnly(Object entityOrProxy) { return session.isReadOnly( entityOrProxy ); } + @Override public void setReadOnly(Object entityOrProxy, boolean readOnly) { session.setReadOnly( entityOrProxy, readOnly ); } + @Override public void doWork(Work work) throws HibernateException { session.doWork( work ); } + @Override public T doReturningWork(ReturningWork work) throws HibernateException { return session.doReturningWork( work ); } + @Override public Connection disconnect() { return session.disconnect(); } + @Override public void reconnect(Connection connection) { session.reconnect( connection ); } + @Override public boolean isFetchProfileEnabled(String name) throws UnknownProfileException { return session.isFetchProfileEnabled( name ); } + @Override public void enableFetchProfile(String name) throws UnknownProfileException { session.enableFetchProfile( name ); } + @Override public void disableFetchProfile(String name) throws UnknownProfileException { session.disableFetchProfile( name ); } + @Override public TypeHelper getTypeHelper() { return session.getTypeHelper(); } + @Override public LobHelper getLobHelper() { return session.getLobHelper(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java index 0e997fc478..aee333ad27 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java @@ -51,6 +51,7 @@ import org.hibernate.id.IdentifierGenerator; import org.hibernate.internal.NamedQueryRepository; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.procedure.ProcedureCallMemento; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.service.spi.ServiceRegistryImplementor; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java index c6b0153c74..4ed1481626 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java @@ -27,6 +27,7 @@ import java.io.Serializable; import java.sql.Connection; import java.sql.SQLException; import java.util.List; +import java.util.Map; import java.util.UUID; import org.hibernate.HibernateException; @@ -59,6 +60,7 @@ import org.hibernate.jdbc.WorkExecutorVisitable; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; +import org.hibernate.procedure.ProcedureCallMemento; import org.hibernate.procedure.internal.ProcedureCallImpl; import org.hibernate.type.Type; @@ -67,11 +69,11 @@ import org.hibernate.type.Type; * * @author Gavin King */ -public abstract class AbstractSessionImpl implements Serializable, SharedSessionContract, - SessionImplementor, TransactionContext { +public abstract class AbstractSessionImpl + implements Serializable, SharedSessionContract, SessionImplementor, TransactionContext { protected transient SessionFactoryImpl factory; private final String tenantIdentifier; - private boolean closed = false; + private boolean closed; protected AbstractSessionImpl(SessionFactoryImpl factory, String tenantIdentifier) { this.factory = factory; @@ -218,10 +220,10 @@ public abstract class AbstractSessionImpl implements Serializable, SharedSession @Override public Query createQuery(String queryString) { errorIfClosed(); - QueryImpl query = new QueryImpl( + final QueryImpl query = new QueryImpl( queryString, - this, - getHQLQueryPlan( queryString, false ).getParameterMetadata() + this, + getHQLQueryPlan( queryString, false ).getParameterMetadata() ); query.setComment( queryString ); return query; @@ -230,15 +232,31 @@ public abstract class AbstractSessionImpl implements Serializable, SharedSession @Override public SQLQuery createSQLQuery(String sql) { errorIfClosed(); - SQLQueryImpl query = new SQLQueryImpl( + final SQLQueryImpl query = new SQLQueryImpl( sql, - this, - factory.getQueryPlanCache().getSQLParameterMetadata( sql ) + this, + factory.getQueryPlanCache().getSQLParameterMetadata( sql ) ); query.setComment( "dynamic native SQL query" ); return query; } + @Override + @SuppressWarnings("UnnecessaryLocalVariable") + public ProcedureCall getNamedProcedureCall(String name) { + errorIfClosed(); + + final ProcedureCallMemento memento = factory.getNamedQueryRepository().getNamedProcedureCallMemento( name ); + if ( memento == null ) { + throw new IllegalArgumentException( + "Could not find named stored procedure call with that registration name : " + name + ); + } + final ProcedureCall procedureCall = memento.makeProcedureCall( this ); +// procedureCall.setComment( "Named stored procedure call [" + name + "]" ); + return procedureCall; + } + @Override @SuppressWarnings("UnnecessaryLocalVariable") public ProcedureCall createStoredProcedureCall(String procedureName) { diff --git a/hibernate-core/src/main/java/org/hibernate/internal/NamedQueryRepository.java b/hibernate-core/src/main/java/org/hibernate/internal/NamedQueryRepository.java index 6dc7bb2425..90866b06ad 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/NamedQueryRepository.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/NamedQueryRepository.java @@ -25,20 +25,20 @@ package org.hibernate.internal; import java.util.Collections; import java.util.HashMap; -import java.util.Iterator; +import java.util.List; import java.util.Map; import org.jboss.logging.Logger; import org.hibernate.HibernateException; import org.hibernate.MappingException; -import org.hibernate.QueryException; import org.hibernate.engine.ResultSetMappingDefinition; import org.hibernate.engine.query.spi.QueryPlanCache; import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification; import org.hibernate.engine.spi.NamedQueryDefinition; import org.hibernate.engine.spi.NamedSQLQueryDefinition; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.procedure.ProcedureCallMemento; /** * @author Steve Ebersole @@ -46,14 +46,17 @@ import org.hibernate.internal.util.collections.CollectionHelper; public class NamedQueryRepository { private static final Logger log = Logger.getLogger( NamedQueryRepository.class ); + private final Map namedSqlResultSetMappingMap; + private volatile Map namedQueryDefinitionMap; private volatile Map namedSqlQueryDefinitionMap; - private final Map namedSqlResultSetMappingMap; + private volatile Map procedureCallMementoMap; public NamedQueryRepository( Iterable namedQueryDefinitions, Iterable namedSqlQueryDefinitions, - Iterable namedSqlResultSetMappings) { + Iterable namedSqlResultSetMappings, + List namedProcedureCalls) { final HashMap namedQueryDefinitionMap = new HashMap(); for ( NamedQueryDefinition namedQueryDefinition : namedQueryDefinitions ) { namedQueryDefinitionMap.put( namedQueryDefinition.getName(), namedQueryDefinition ); @@ -83,6 +86,10 @@ public class NamedQueryRepository { return namedSqlQueryDefinitionMap.get( queryName ); } + public ProcedureCallMemento getNamedProcedureCallMemento(String name) { + return procedureCallMementoMap.get( name ); + } + public ResultSetMappingDefinition getResultSetMappingDefinition(String mappingName) { return namedSqlResultSetMappingMap.get( mappingName ); } @@ -127,6 +134,20 @@ public class NamedQueryRepository { this.namedSqlQueryDefinitionMap = Collections.unmodifiableMap( copy ); } + public synchronized void registerNamedProcedureCallMemento(String name, ProcedureCallMemento memento) { + final Map copy = CollectionHelper.makeCopy( procedureCallMementoMap ); + final ProcedureCallMemento previous = copy.put( name, memento ); + if ( previous != null ) { + log.debugf( + "registering named procedure call definition [%s] overriding previously registered definition [%s]", + name, + previous + ); + } + + this.procedureCallMementoMap = Collections.unmodifiableMap( copy ); + } + public Map checkNamedQueries(QueryPlanCache queryPlanCache) { Map errors = new HashMap(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index 45b2529fa1..3f191d8a8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -35,6 +35,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -82,6 +83,7 @@ import org.hibernate.cfg.Configuration; import org.hibernate.cfg.Environment; import org.hibernate.cfg.Settings; import org.hibernate.cfg.SettingsFactory; +import org.hibernate.cfg.annotations.NamedProcedureCallDefinition; import org.hibernate.context.internal.JTASessionContext; import org.hibernate.context.internal.ManagedSessionContext; import org.hibernate.context.internal.ThreadLocalSessionContext; @@ -133,6 +135,7 @@ import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Loadable; import org.hibernate.persister.entity.Queryable; import org.hibernate.persister.spi.PersisterFactory; +import org.hibernate.procedure.ProcedureCallMemento; import org.hibernate.proxy.EntityNotFoundDelegate; import org.hibernate.service.ServiceRegistry; import org.hibernate.service.spi.ServiceRegistryImplementor; @@ -455,7 +458,8 @@ public final class SessionFactoryImpl this.namedQueryRepository = new NamedQueryRepository( cfg.getNamedQueries().values(), cfg.getNamedSQLQueries().values(), - cfg.getSqlResultSetMappings().values() + cfg.getSqlResultSetMappings().values(), + toProcedureCallMementos( cfg.getNamedProcedureCallMap(), cfg.getSqlResultSetMappings() ) ); imports = new HashMap( cfg.getImports() ); @@ -576,6 +580,18 @@ public final class SessionFactoryImpl this.observer.sessionFactoryCreated( this ); } + private List toProcedureCallMementos( + Map definitions, + Map resultSetMappingMap) { + final List rtn = new ArrayList(); + if ( definitions != null ) { + for ( NamedProcedureCallDefinition definition : definitions.values() ) { + rtn.add( definition.toMemento( this, resultSetMappingMap ) ); + } + } + return rtn; + } + private JdbcConnectionAccess buildLocalConnectionAccess() { return new JdbcConnectionAccess() { @Override @@ -854,7 +870,8 @@ public final class SessionFactoryImpl namedQueryRepository = new NamedQueryRepository( metadata.getNamedQueryDefinitions(), metadata.getNamedNativeQueryDefinitions(), - metadata.getResultSetMappingDefinitions() + metadata.getResultSetMappingDefinitions(), + null ); imports = new HashMap(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java index 377622d2b6..ff56575229 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -131,6 +132,17 @@ public final class CollectionHelper { return new ArrayList( anticipatedSize ); } + public static Set makeCopy(Set source) { + if ( source == null ) { + return null; + } + + final int size = source.size(); + final Set copy = new HashSet( size + 1 ); + copy.addAll( source ); + return copy; + } + public static boolean isEmpty(Collection collection) { return collection == null || collection.isEmpty(); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java index fe2cc4713d..61921e41ef 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java @@ -24,7 +24,10 @@ */ package org.hibernate.loader.custom.sql; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -56,9 +59,12 @@ import org.hibernate.loader.custom.NonScalarReturn; import org.hibernate.loader.custom.Return; import org.hibernate.loader.custom.RootReturn; import org.hibernate.loader.custom.ScalarReturn; +import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.SQLLoadableCollection; import org.hibernate.persister.entity.EntityPersister; +import org.hibernate.persister.entity.Joinable; import org.hibernate.persister.entity.SQLLoadable; +import org.hibernate.type.AssociationType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; @@ -84,10 +90,10 @@ public class SQLQueryReturnProcessor { private final Map alias2Return = new HashMap(); private final Map alias2OwnerAlias = new HashMap(); - private final Map alias2Persister = new HashMap(); + private final Map alias2Persister = new HashMap(); private final Map alias2Suffix = new HashMap(); - private final Map alias2CollectionPersister = new HashMap(); + private final Map alias2CollectionPersister = new HashMap(); private final Map alias2CollectionSuffix = new HashMap(); private final Map entityPropertyResultMaps = new HashMap(); @@ -112,7 +118,7 @@ public class SQLQueryReturnProcessor { this.factory = factory; } - /*package*/ class ResultAliasContext { + public class ResultAliasContext { public SQLLoadable getEntityPersister(String alias) { return ( SQLLoadable ) alias2Persister.get( alias ); } @@ -136,6 +142,25 @@ public class SQLQueryReturnProcessor { public Map getPropertyResultsMap(String alias) { return internalGetPropertyResultsMap( alias ); } + + public String[] collectQuerySpaces() { + final HashSet spaces = new HashSet(); + collectQuerySpaces( spaces ); + return spaces.toArray( new String[ spaces.size() ] ); + } + + public void collectQuerySpaces(Collection spaces) { + for ( EntityPersister persister : alias2Persister.values() ) { + Collections.addAll( spaces, (String[]) persister.getQuerySpaces() ); + } + for ( CollectionPersister persister : alias2CollectionPersister.values() ) { + final Type elementType = persister.getElementType(); + if ( elementType.isEntityType() && ! elementType.isAnyType() ) { + final Joinable joinable = ( (EntityType) elementType ).getAssociatedJoinable( factory ); + Collections.addAll( spaces, (String[]) ( (EntityPersister) joinable ).getQuerySpaces() ); + } + } + } } private Map internalGetPropertyResultsMap(String alias) { diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java index 3ad02c4013..62ad4f01ed 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCall.java @@ -25,6 +25,7 @@ package org.hibernate.procedure; import javax.persistence.ParameterMode; import java.util.List; +import java.util.Map; import org.hibernate.BasicQueryContract; import org.hibernate.MappingException; @@ -58,6 +59,7 @@ public interface ProcedureCall extends BasicQueryContract, SynchronizeableQuery * @param position The position * @param type The Java type of the parameter * @param mode The parameter mode (in, out, inout) + * @param The parameterized Java type of the parameter. * * @return The parameter registration memento */ @@ -89,8 +91,12 @@ public interface ProcedureCall extends BasicQueryContract, SynchronizeableQuery * @param parameterName The parameter name * @param type The Java type of the parameter * @param mode The parameter mode (in, out, inout) + * @param The parameterized Java type of the parameter. * * @return The parameter registration memento + * + * @throws NamedParametersNotSupportedException When the underlying database is known to not support + * named procedure parameters. */ public ParameterRegistration registerParameter(String parameterName, Class type, ParameterMode mode) throws NamedParametersNotSupportedException; @@ -103,6 +109,9 @@ public interface ProcedureCall extends BasicQueryContract, SynchronizeableQuery * @param mode The parameter mode (in, out, inout) * * @return The parameter registration memento + * + * @throws NamedParametersNotSupportedException When the underlying database is known to not support + * named procedure parameters. */ public ProcedureCall registerParameter0(String parameterName, Class type, ParameterMode mode) throws NamedParametersNotSupportedException; @@ -133,4 +142,12 @@ public interface ProcedureCall extends BasicQueryContract, SynchronizeableQuery */ public ProcedureResult getResult(); + /** + * Extract the disconnected representation of this call. Used in HEM to allow redefining a named query + * + * @param hints The hints to incorporate into the memento + * + * @return The memento + */ + public ProcedureCallMemento extractMemento(Map hints); } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCallMemento.java b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCallMemento.java new file mode 100644 index 0000000000..45d582d8cf --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/ProcedureCallMemento.java @@ -0,0 +1,63 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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; + +import java.util.Map; + +import org.hibernate.Session; +import org.hibernate.engine.spi.SessionImplementor; + +/** + * Represents a "memento" of a ProcedureCall + * + * @author Steve Ebersole + */ +public interface ProcedureCallMemento { + /** + * Convert the memento back into an executable (connected) form. + * + * @param session The session to connect the procedure call to + * + * @return The executable call + */ + public ProcedureCall makeProcedureCall(Session session); + + /** + * Convert the memento back into an executable (connected) form. + * + * @param session The session to connect the procedure call to + * + * @return The executable call + */ + public ProcedureCall makeProcedureCall(SessionImplementor session); + + /** + * Access to any hints associated with the memento. + *

+ * IMPL NOTE : exposed separately because only HEM needs access to this. + * + * @return The hints. + */ + public Map getHintsMap(); +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java index 5af4950331..21081e5681 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/AbstractParameterRegistrationImpl.java @@ -43,6 +43,8 @@ import org.hibernate.type.ProcedureParameterExtractionAware; import org.hibernate.type.Type; /** + * Abstract implementation of ParameterRegistration/ParameterRegistrationImplementor + * * @author Steve Ebersole */ public abstract class AbstractParameterRegistrationImpl implements ParameterRegistrationImplementor { @@ -62,28 +64,53 @@ public abstract class AbstractParameterRegistrationImpl implements ParameterR private Type hibernateType; private int[] sqlTypes; + + // positional constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + protected AbstractParameterRegistrationImpl( ProcedureCallImpl procedureCall, Integer position, + ParameterMode mode, + Class type) { + this( procedureCall, position, null, mode, type ); + } + + protected AbstractParameterRegistrationImpl( + ProcedureCallImpl procedureCall, + Integer position, + ParameterMode mode, Class type, - ParameterMode mode) { - this( procedureCall, position, null, type, mode ); + Type hibernateType) { + this( procedureCall, position, null, mode, type, hibernateType ); + } + + + // named constructors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + protected AbstractParameterRegistrationImpl( + ProcedureCallImpl procedureCall, + String name, + ParameterMode mode, + Class type) { + this( procedureCall, null, name, mode, type ); } protected AbstractParameterRegistrationImpl( ProcedureCallImpl procedureCall, String name, + ParameterMode mode, Class type, - ParameterMode mode) { - this( procedureCall, null, name, type, mode ); + Type hibernateType) { + this( procedureCall, null, name, mode, type, hibernateType ); } private AbstractParameterRegistrationImpl( ProcedureCallImpl procedureCall, Integer position, String name, + ParameterMode mode, Class type, - ParameterMode mode) { + Type hibernateType) { this.procedureCall = procedureCall; this.position = position; @@ -92,7 +119,23 @@ public abstract class AbstractParameterRegistrationImpl implements ParameterR this.mode = mode; this.type = type; - setHibernateType( session().getFactory().getTypeResolver().heuristicType( type.getName() ) ); + setHibernateType( hibernateType ); + } + + private AbstractParameterRegistrationImpl( + ProcedureCallImpl procedureCall, + Integer position, + String name, + ParameterMode mode, + Class type) { + this( + procedureCall, + position, + name, + mode, + type, + procedureCall.getSession().getFactory().getTypeResolver().heuristicType( type.getName() ) + ); } protected SessionImplementor session() { @@ -119,6 +162,11 @@ public abstract class AbstractParameterRegistrationImpl implements ParameterR return mode; } + @Override + public Type getHibernateType() { + return hibernateType; + } + @Override public void setHibernateType(Type type) { if ( type == null ) { @@ -129,6 +177,7 @@ public abstract class AbstractParameterRegistrationImpl implements ParameterR } @Override + @SuppressWarnings("unchecked") public ParameterBind getBind() { return bind; } @@ -175,11 +224,12 @@ public abstract class AbstractParameterRegistrationImpl implements ParameterR if ( mode == ParameterMode.IN || mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { if ( mode == ParameterMode.INOUT || mode == ParameterMode.OUT ) { if ( sqlTypes.length > 1 ) { - if ( ProcedureParameterExtractionAware.class.isInstance( hibernateType ) - && ( (ProcedureParameterExtractionAware) hibernateType ).canDoExtraction() ) { - // the type can handle multi-param extraction... - } - else { + // there is more than one column involved; see if the Hibernate Type can handle + // multi-param extraction... + final boolean canHandleMultiParamExtraction = + ProcedureParameterExtractionAware.class.isInstance( hibernateType ) + && ( (ProcedureParameterExtractionAware) hibernateType ).canDoExtraction(); + if ( ! canHandleMultiParamExtraction ) { // it cannot... throw new UnsupportedOperationException( "Type [" + hibernateType + "] does support multi-parameter value extraction" diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java index d3a772cb70..6f9321426a 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/NamedParameterRegistration.java @@ -25,15 +25,28 @@ package org.hibernate.procedure.internal; import javax.persistence.ParameterMode; +import org.hibernate.type.Type; + /** + * Represents a registered named parameter + * * @author Steve Ebersole */ public class NamedParameterRegistration extends AbstractParameterRegistrationImpl { - public NamedParameterRegistration( + NamedParameterRegistration( ProcedureCallImpl procedureCall, String name, + ParameterMode mode, + Class type) { + super( procedureCall, name, mode, type ); + } + + NamedParameterRegistration( + ProcedureCallImpl procedureCall, + String name, + ParameterMode mode, Class type, - ParameterMode mode) { - super( procedureCall, name, type, mode ); + Type hibernateType) { + super( procedureCall, name, mode, type, hibernateType ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterRegistrationImplementor.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterRegistrationImplementor.java index 0d1992d8a3..e349e6649e 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterRegistrationImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterRegistrationImplementor.java @@ -27,15 +27,45 @@ import java.sql.CallableStatement; import java.sql.SQLException; import org.hibernate.procedure.ParameterRegistration; +import org.hibernate.type.Type; /** + * Additional internal contract for ParameterRegistration + * * @author Steve Ebersole */ public interface ParameterRegistrationImplementor extends ParameterRegistration { + /** + * Prepare for execution. + * + * @param statement The statement about to be executed + * @param i The parameter index for this registration (used for positional) + * + * @throws SQLException Indicates a problem accessing the statement object + */ public void prepare(CallableStatement statement, int i) throws SQLException; + /** + * Access to the Hibernate type for this parameter registration + * + * @return The Hibernate Type + */ + public Type getHibernateType(); + + /** + * Access to the SQL type(s) for this parameter + * + * @return The SQL types (JDBC type codes) + */ public int[] getSqlTypes(); + /** + * Extract value from the statement after execution (used for OUT/INOUT parameters). + * + * @param statement The callable statement + * + * @return The extracted value + */ public T extract(CallableStatement statement); } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterStrategy.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterStrategy.java index bf517dc3f5..f329d46208 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ParameterStrategy.java @@ -27,7 +27,16 @@ package org.hibernate.procedure.internal; * The style/strategy of parameter registration used in a particular procedure call definition. */ public enum ParameterStrategy { + /** + * The parameters are named + */ NAMED, + /** + * The parameters are positional + */ POSITIONAL, + /** + * We do not (yet) know + */ UNKNOWN } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java index a991525c58..64e63aab35 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/PositionalParameterRegistration.java @@ -25,15 +25,28 @@ package org.hibernate.procedure.internal; import javax.persistence.ParameterMode; +import org.hibernate.type.Type; + /** + * Represents a registered positional parameter + * * @author Steve Ebersole */ public class PositionalParameterRegistration extends AbstractParameterRegistrationImpl { - public PositionalParameterRegistration( + PositionalParameterRegistration( ProcedureCallImpl procedureCall, Integer position, + ParameterMode mode, + Class type) { + super( procedureCall, position, mode, type ); + } + + PositionalParameterRegistration( + ProcedureCallImpl procedureCall, + Integer position, + ParameterMode mode, Class type, - ParameterMode mode) { - super( procedureCall, position, type, mode ); + Type hibernateType) { + super( procedureCall, position, mode, type, hibernateType ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java index 0f82a10e6c..0c878894d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -31,25 +31,28 @@ 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 org.jboss.logging.Logger; + import org.hibernate.HibernateException; -import org.hibernate.LockMode; -import org.hibernate.MappingException; 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.query.spi.sql.NativeSQLQueryRootReturn; 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.util.StringHelper; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.procedure.ProcedureCall; import org.hibernate.procedure.NamedParametersNotSupportedException; import org.hibernate.procedure.ParameterRegistration; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.procedure.ProcedureCallMemento; import org.hibernate.procedure.ProcedureResult; import org.hibernate.result.spi.ResultContext; import org.hibernate.type.Type; @@ -60,6 +63,10 @@ import org.hibernate.type.Type; * @author Steve Ebersole */ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements ProcedureCall, ResultContext { + private static final Logger log = Logger.getLogger( ProcedureCallImpl.class ); + + private static final NativeSQLQueryReturn[] NO_RETURNS = new NativeSQLQueryReturn[0]; + private final String procedureName; private final NativeSQLQueryReturn[] queryReturns; @@ -70,74 +77,167 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements private ProcedureResultImpl outputs; - - @SuppressWarnings("unchecked") + /** + * The no-returns form. + * + * @param session The session + * @param procedureName The name of the procedure to call + */ public ProcedureCallImpl(SessionImplementor session, String procedureName) { - this( session, procedureName, (List) null ); + super( session ); + this.procedureName = procedureName; + this.queryReturns = NO_RETURNS; } - public ProcedureCallImpl(SessionImplementor session, String procedureName, List queryReturns) { + /** + * 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; - if ( queryReturns == null || queryReturns.isEmpty() ) { - this.queryReturns = new NativeSQLQueryReturn[0]; - } - else { - this.queryReturns = queryReturns.toArray( new NativeSQLQueryReturn[ queryReturns.size() ] ); - } + 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; } - public ProcedureCallImpl(SessionImplementor session, String procedureName, Class... resultClasses) { - this( session, procedureName, collectQueryReturns( resultClasses ) ); + /** + * 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; } - private static List collectQueryReturns(Class[] resultClasses) { - if ( resultClasses == null || resultClasses.length == 0 ) { - return null; + /** + * 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; } - List queryReturns = new ArrayList( resultClasses.length ); - int i = 1; - for ( Class resultClass : resultClasses ) { - queryReturns.add( new NativeSQLQueryRootReturn( "alias" + i, resultClass.getName(), LockMode.READ ) ); - i++; - } - return queryReturns; - } - - public ProcedureCallImpl(SessionImplementor session, String procedureName, String... resultSetMappings) { - this( session, procedureName, collectQueryReturns( session, resultSetMappings ) ); - } - - private static List collectQueryReturns(SessionImplementor session, String[] resultSetMappings) { - if ( resultSetMappings == null || resultSetMappings.length == 0 ) { - return null; + 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; } - List queryReturns = new ArrayList( resultSetMappings.length ); - for ( String resultSetMapping : resultSetMappings ) { - ResultSetMappingDefinition mapping = session.getFactory().getResultSetMapping( resultSetMapping ); - if ( mapping == null ) { - throw new MappingException( "Unknown SqlResultSetMapping [" + resultSetMapping + "]" ); + 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() + ); } - queryReturns.addAll( Arrays.asList( mapping.getQueryReturns() ) ); + 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 ); } - return queryReturns; + this.registeredParameters = parameterRegistrations; } -// public ProcedureCallImpl( -// SessionImplementor session, -// String procedureName, -// List parameters) { -// // this form is intended for named stored procedure calls. -// // todo : introduce a NamedProcedureCallDefinition object to hold all needed info and pass that in here; will help with EM.addNamedQuery as well.. -// this( session, procedureName ); -// for ( StoredProcedureParameter parameter : parameters ) { -// registerParameter( (StoredProcedureParameterImplementor) parameter ); -// } -// } - @Override public SessionImplementor getSession() { return super.session(); @@ -165,7 +265,8 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements @Override @SuppressWarnings("unchecked") public ParameterRegistration registerParameter(int position, Class type, ParameterMode mode) { - final PositionalParameterRegistration parameterRegistration = new PositionalParameterRegistration( this, position, type, mode ); + final PositionalParameterRegistration parameterRegistration = + new PositionalParameterRegistration( this, position, mode, type ); registerParameter( parameterRegistration ); return parameterRegistration; } @@ -233,7 +334,8 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements @Override @SuppressWarnings("unchecked") public ParameterRegistration registerParameter(String name, Class type, ParameterMode mode) { - final NamedParameterRegistration parameterRegistration = new NamedParameterRegistration( this, name, type, mode ); + final NamedParameterRegistration parameterRegistration + = new NamedParameterRegistration( this, name, mode, type ); registerParameter( parameterRegistration ); return parameterRegistration; } @@ -329,6 +431,12 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements 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(); @@ -374,6 +482,7 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements return buildQueryParametersObject(); } + @Override public QueryParameters buildQueryParametersObject() { QueryParameters qp = super.buildQueryParametersObject(); // both of these are for documentation purposes, they are actually handled directly... @@ -382,8 +491,13 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements return qp; } + /** + * Collects any parameter registrations which indicate a REF_CURSOR parameter type/mode. + * + * @return The collected REF_CURSOR type parameters. + */ public ParameterRegistrationImplementor[] collectRefCursorParameters() { - List refCursorParams = new ArrayList(); + final List refCursorParams = new ArrayList(); for ( ParameterRegistrationImplementor param : registeredParameters ) { if ( param.getMode() == ParameterMode.REF_CURSOR ) { refCursorParams.add( param ); @@ -391,4 +505,29 @@ public class ProcedureCallImpl extends AbstractBasicQueryContractImpl implements } 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; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java new file mode 100644 index 0000000000..7af100283e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallMementoImpl.java @@ -0,0 +1,169 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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 javax.persistence.ParameterMode; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.hibernate.Session; +import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.procedure.ProcedureCall; +import org.hibernate.procedure.ProcedureCallMemento; +import org.hibernate.type.Type; + +/** + * Implementation of ProcedureCallMemento + * + * @author Steve Ebersole + */ +public class ProcedureCallMementoImpl implements ProcedureCallMemento { + private final String procedureName; + private final NativeSQLQueryReturn[] queryReturns; + + private final ParameterStrategy parameterStrategy; + private final List parameterDeclarations; + + private final Set synchronizedQuerySpaces; + + private final Map hintsMap; + + public ProcedureCallMementoImpl( + String procedureName, + NativeSQLQueryReturn[] queryReturns, + ParameterStrategy parameterStrategy, + List parameterDeclarations, + Set synchronizedQuerySpaces, + Map hintsMap) { + this.procedureName = procedureName; + this.queryReturns = queryReturns; + this.parameterStrategy = parameterStrategy; + this.parameterDeclarations = parameterDeclarations; + this.synchronizedQuerySpaces = synchronizedQuerySpaces; + this.hintsMap = hintsMap; + } + + @Override + public ProcedureCall makeProcedureCall(Session session) { + return new ProcedureCallImpl( (SessionImplementor) session, this ); + } + + @Override + public ProcedureCall makeProcedureCall(SessionImplementor session) { + return new ProcedureCallImpl( session, this ); + } + + public String getProcedureName() { + return procedureName; + } + + public NativeSQLQueryReturn[] getQueryReturns() { + return queryReturns; + } + + public ParameterStrategy getParameterStrategy() { + return parameterStrategy; + } + + public List getParameterDeclarations() { + return parameterDeclarations; + } + + public Set getSynchronizedQuerySpaces() { + return synchronizedQuerySpaces; + } + + @Override + public Map getHintsMap() { + return hintsMap; + } + + /** + * A "disconnected" copy of the metadata for a parameter, that can be used in ProcedureCallMementoImpl. + */ + public static class ParameterMemento { + private final Integer position; + private final String name; + private final ParameterMode mode; + private final Class type; + private final Type hibernateType; + + /** + * Create the memento + * + * @param position The parameter position + * @param name The parameter name + * @param mode The parameter mode + * @param type The Java type of the parameter + * @param hibernateType The Hibernate Type. + */ + public ParameterMemento(int position, String name, ParameterMode mode, Class type, Type hibernateType) { + this.position = position; + this.name = name; + this.mode = mode; + this.type = type; + this.hibernateType = hibernateType; + } + + public Integer getPosition() { + return position; + } + + public String getName() { + return name; + } + + public ParameterMode getMode() { + return mode; + } + + public Class getType() { + return type; + } + + public Type getHibernateType() { + return hibernateType; + } + + /** + * Build a ParameterMemento from the given parameter registration + * + * @param registration The parameter registration from a ProcedureCall + * + * @return The memento + */ + public static ParameterMemento fromRegistration(ParameterRegistrationImplementor registration) { + return new ParameterMemento( + registration.getPosition(), + registration.getName(), + registration.getMode(), + registration.getType(), + registration.getHibernateType() + ); + } + + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java new file mode 100644 index 0000000000..36e6ffe66f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/Util.java @@ -0,0 +1,116 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2013, 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.util.Collections; +import java.util.Map; +import java.util.Set; + +import org.hibernate.LockMode; +import org.hibernate.MappingException; +import org.hibernate.engine.ResultSetMappingDefinition; +import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn; +import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.loader.custom.sql.SQLQueryReturnProcessor; +import org.hibernate.persister.entity.EntityPersister; + +/** + * @author Steve Ebersole + */ +public class Util { + private Util() { + } + + /** + * Makes a copy of the given query return array. + * + * @param queryReturns The returns to copy + * + * @return The copy + */ + static NativeSQLQueryReturn[] copy(NativeSQLQueryReturn[] queryReturns) { + if ( queryReturns == null ) { + return new NativeSQLQueryReturn[0]; + } + + final NativeSQLQueryReturn[] copy = new NativeSQLQueryReturn[ queryReturns.length ]; + System.arraycopy( queryReturns, 0, copy, 0, queryReturns.length ); + return copy; + } + + public static Set copy(Set synchronizedQuerySpaces) { + return CollectionHelper.makeCopy( synchronizedQuerySpaces ); + } + + public static Map copy(Map hints) { + return CollectionHelper.makeCopy( hints ); + } + + public static interface ResultSetMappingResolutionContext { + public SessionFactoryImplementor getSessionFactory(); + public ResultSetMappingDefinition findResultSetMapping(String name); + public void addQueryReturns(NativeSQLQueryReturn... queryReturns); + public void addQuerySpaces(String... spaces); + } + + public static void resolveResultSetMappings(ResultSetMappingResolutionContext context, String... resultSetMappingNames) { + for ( String resultSetMappingName : resultSetMappingNames ) { + final ResultSetMappingDefinition mapping = context.findResultSetMapping( resultSetMappingName ); + if ( mapping == null ) { + throw new MappingException( "Unknown SqlResultSetMapping [" + resultSetMappingName + "]" ); + } + + context.addQueryReturns( mapping.getQueryReturns() ); + + final SQLQueryReturnProcessor processor = + new SQLQueryReturnProcessor( mapping.getQueryReturns(), context.getSessionFactory() ); + final SQLQueryReturnProcessor.ResultAliasContext processResult = processor.process(); + context.addQuerySpaces( processResult.collectQuerySpaces() ); + } + } + + public static interface ResultClassesResolutionContext { + public SessionFactoryImplementor getSessionFactory(); + public void addQueryReturns(NativeSQLQueryReturn... queryReturns); + public void addQuerySpaces(String... spaces); + } + + public static void resolveResultClasses(ResultClassesResolutionContext context, Class... resultClasses) { + int i = 1; + for ( Class resultClass : resultClasses ) { + context.addQueryReturns( + new NativeSQLQueryRootReturn( "alias" + i, resultClass.getName(), LockMode.READ ) + ); + try { + final EntityPersister persister = context.getSessionFactory().getEntityPersister( resultClass.getName() ); + context.addQuerySpaces( (String[]) persister.getQuerySpaces() ); + } + catch (Exception ignore) { + + } + } + } +} diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerFactoryImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerFactoryImpl.java index a94c3228aa..07da96f48b 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerFactoryImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/EntityManagerFactoryImpl.java @@ -75,6 +75,7 @@ import org.hibernate.jpa.internal.metamodel.MetamodelImpl; import org.hibernate.jpa.internal.util.PersistenceUtilHelper; import org.hibernate.mapping.PersistentClass; import org.hibernate.metadata.ClassMetadata; +import org.hibernate.procedure.ProcedureCall; import org.hibernate.service.ServiceRegistry; /** @@ -284,19 +285,25 @@ public class EntityManagerFactoryImpl implements HibernateEntityManagerFactory { throw new IllegalStateException( "EntityManagerFactory is closed" ); } - if ( ! HibernateQuery.class.isInstance( query ) ) { + if ( StoredProcedureQueryImpl.class.isInstance( query ) ) { + final ProcedureCall procedureCall = ( (StoredProcedureQueryImpl) query ).getHibernateProcedureCall(); + sessionFactory.getNamedQueryRepository().registerNamedProcedureCallMemento( name, procedureCall.extractMemento( query.getHints() ) ); + } + else if ( ! HibernateQuery.class.isInstance( query ) ) { throw new PersistenceException( "Cannot use query non-Hibernate EntityManager query as basis for named query" ); } - - // create and register the proper NamedQueryDefinition... - final org.hibernate.Query hibernateQuery = ( (HibernateQuery) query ).getHibernateQuery(); - if ( org.hibernate.SQLQuery.class.isInstance( hibernateQuery ) ) { - final NamedSQLQueryDefinition namedQueryDefinition = extractSqlQueryDefinition( ( org.hibernate.SQLQuery ) hibernateQuery, name ); - sessionFactory.registerNamedSQLQueryDefinition( name, namedQueryDefinition ); - } else { - final NamedQueryDefinition namedQueryDefinition = extractHqlQueryDefinition( hibernateQuery, name ); - sessionFactory.registerNamedQueryDefinition( name, namedQueryDefinition ); + // create and register the proper NamedQueryDefinition... + final org.hibernate.Query hibernateQuery = ( (HibernateQuery) query ).getHibernateQuery(); + if ( org.hibernate.SQLQuery.class.isInstance( hibernateQuery ) ) { + sessionFactory.registerNamedSQLQueryDefinition( + name, + extractSqlQueryDefinition( (org.hibernate.SQLQuery) hibernateQuery, name ) + ); + } + else { + sessionFactory.registerNamedQueryDefinition( name, extractHqlQueryDefinition( hibernateQuery, name ) ); + } } } diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java index aa9ddd912b..b232696774 100644 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/internal/StoredProcedureQueryImpl.java @@ -298,6 +298,10 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro return false; } + public ProcedureCall getHibernateProcedureCall() { + return procedureCall; + } + private static class ParameterRegistrationImpl implements ParameterRegistration { private final org.hibernate.procedure.ParameterRegistration nativeParamRegistration; diff --git a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractEntityManagerImpl.java b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractEntityManagerImpl.java index e7745f4081..369bbbe829 100755 --- a/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractEntityManagerImpl.java +++ b/hibernate-entitymanager/src/main/java/org/hibernate/jpa/spi/AbstractEntityManagerImpl.java @@ -124,6 +124,7 @@ import org.hibernate.jpa.internal.TransactionImpl; import org.hibernate.jpa.internal.util.CacheModeHelper; import org.hibernate.jpa.internal.util.ConfigurationHelper; import org.hibernate.jpa.internal.util.LockModeTypeHelper; +import org.hibernate.procedure.ProcedureCallMemento; import org.hibernate.proxy.HibernateProxy; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.transform.BasicTransformerAdapter; @@ -820,7 +821,7 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage public Query createNativeQuery(String sqlString, String resultSetMapping) { checkOpen(); try { - SQLQuery q = internalGetSession().createSQLQuery( sqlString ); + final SQLQuery q = internalGetSession().createSQLQuery( sqlString ); q.setResultSetMapping( resultSetMapping ); return new QueryImpl( q, this ); } @@ -832,15 +833,30 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage @Override public StoredProcedureQuery createNamedStoredProcedureQuery(String name) { checkOpen(); - throw new NotYetImplementedException(); + final ProcedureCallMemento memento = ( (SessionImplementor) internalGetSession() ).getFactory() + .getNamedQueryRepository().getNamedProcedureCallMemento( name ); + if ( memento == null ) { + throw new IllegalArgumentException( "No @NamedStoredProcedureQuery was found with that name : " + name ); + } + final ProcedureCall procedureCall = memento.makeProcedureCall( internalGetSession() ); + final StoredProcedureQueryImpl jpaImpl = new StoredProcedureQueryImpl( procedureCall, this ); + // apply hints + if ( memento.getHintsMap() != null ) { + for ( Map.Entry hintEntry : memento.getHintsMap().entrySet() ) { + jpaImpl.setHint( hintEntry.getKey(), hintEntry.getValue() ); + } + } + return jpaImpl; } @Override public StoredProcedureQuery createStoredProcedureQuery(String procedureName) { checkOpen(); try { - ProcedureCall procedureCall = internalGetSession().createStoredProcedureCall( procedureName ); - return new StoredProcedureQueryImpl( procedureCall, this ); + return new StoredProcedureQueryImpl( + internalGetSession().createStoredProcedureCall( procedureName ), + this + ); } catch ( HibernateException he ) { throw convert( he ); @@ -851,8 +867,10 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage public StoredProcedureQuery createStoredProcedureQuery(String procedureName, Class... resultClasses) { checkOpen(); try { - ProcedureCall procedureCall = internalGetSession().createStoredProcedureCall( procedureName, resultClasses ); - return new StoredProcedureQueryImpl( procedureCall, this ); + return new StoredProcedureQueryImpl( + internalGetSession().createStoredProcedureCall( procedureName, resultClasses ), + this + ); } catch ( HibernateException he ) { throw convert( he ); @@ -862,7 +880,15 @@ public abstract class AbstractEntityManagerImpl implements HibernateEntityManage @Override public StoredProcedureQuery createStoredProcedureQuery(String procedureName, String... resultSetMappings) { checkOpen(); - throw new NotYetImplementedException(); + try { + return new StoredProcedureQueryImpl( + internalGetSession().createStoredProcedureCall( procedureName, resultSetMappings ), + this + ); + } + catch ( HibernateException he ) { + throw convert( he ); + } } @Override