HHH-9060 - JPA 2.1 ContructorResult binding in metamodel

This commit is contained in:
Steve Ebersole 2014-03-17 18:31:42 -05:00
parent 8309071f3c
commit 8945e9e590
2 changed files with 150 additions and 80 deletions

View File

@ -36,13 +36,14 @@
import org.hibernate.MappingException; import org.hibernate.MappingException;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.engine.ResultSetMappingDefinition; import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryScalarReturn; import org.hibernate.engine.query.spi.sql.NativeSQLQueryScalarReturn;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.source.internal.annotations.AnnotationBindingContext; import org.hibernate.metamodel.source.internal.annotations.AnnotationBindingContext;
import org.hibernate.metamodel.source.internal.annotations.util.JPADotNames;
import org.hibernate.metamodel.source.internal.annotations.util.JandexHelper; import org.hibernate.metamodel.source.internal.annotations.util.JandexHelper;
import org.hibernate.metamodel.spi.binding.AttributeBinding; import org.hibernate.metamodel.spi.binding.AttributeBinding;
import org.hibernate.metamodel.spi.binding.CompositeAttributeBinding; import org.hibernate.metamodel.spi.binding.CompositeAttributeBinding;
@ -52,105 +53,144 @@
import org.hibernate.metamodel.spi.binding.SingularAttributeBinding; import org.hibernate.metamodel.spi.binding.SingularAttributeBinding;
import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationInstance;
import org.jboss.logging.Logger; import org.jboss.jandex.AnnotationValue;
import static org.hibernate.metamodel.source.internal.annotations.util.JPADotNames.SQL_RESULT_SET_MAPPING;
import static org.hibernate.metamodel.source.internal.annotations.util.JPADotNames.SQL_RESULT_SET_MAPPINGS;
/** /**
* Binds <ul> * Handles processing of SQL ResultSet mappings as defined via
* <li>{@link javax.persistence.SqlResultSetMapping}</li> * {@link javax.persistence.SqlResultSetMapping} and
* <li>{@link javax.persistence.SqlResultSetMappings}</li> * {@link javax.persistence.SqlResultSetMappings} annotations, including
* <li>{@link javax.persistence.EntityResult}</li> * their related annotations:<ul>
* <li>{@link javax.persistence.FieldResult}</li> * <li>
* <li>{@link javax.persistence.ColumnResult}</li> * {@link javax.persistence.EntityResult} (and
* {@link javax.persistence.FieldResult})
* </li>
* <li>
* {@link javax.persistence.ColumnResult}
* </li>
* <li>
* {@link javax.persistence.ConstructorResult}
* </li>
* </ul> * </ul>
* *
* @author Strong Liu <stliu@hibernate.org> * @author Strong Liu
* @author Steve Ebersole
*/ */
public class SqlResultSetProcessor { public class SqlResultSetProcessor {
private static final CoreMessageLogger LOG = Logger.getMessageLogger( private static final CoreMessageLogger LOG = CoreLogging.messageLogger( QueryProcessor.class );
CoreMessageLogger.class,
QueryProcessor.class.getName()
);
/**
* Disallow direct instantiation.
*/
private SqlResultSetProcessor() { private SqlResultSetProcessor() {
} }
public static void bind(final AnnotationBindingContext bindingContext) { /**
Collection<AnnotationInstance> annotations = bindingContext.getIndex() * Main entry point into processing SQL ResultSet mappings
.getAnnotations( JPADotNames.SQL_RESULT_SET_MAPPING ); *
for ( final AnnotationInstance sqlResultSetMappingAnnotationInstance : annotations ) { * @param bindingContext The binding context
bindSqlResultSetMapping( bindingContext, sqlResultSetMappingAnnotationInstance ); */
public static void bind(AnnotationBindingContext bindingContext) {
// singular form
{
final Collection<AnnotationInstance> sqlResultSetMappingAnnotations =
bindingContext.getIndex().getAnnotations( SQL_RESULT_SET_MAPPING );
for ( final AnnotationInstance sqlResultSetMappingAnnotation : sqlResultSetMappingAnnotations ) {
bindSqlResultSetMapping( sqlResultSetMappingAnnotation, bindingContext );
}
} }
annotations = bindingContext.getIndex().getAnnotations( JPADotNames.SQL_RESULT_SET_MAPPINGS ); // plural form
for ( final AnnotationInstance sqlResultSetMappingsAnnotationInstance : annotations ) { {
for ( AnnotationInstance annotationInstance : JandexHelper.getValue( final Collection<AnnotationInstance> sqlResultSetMappingsAnnotations =
sqlResultSetMappingsAnnotationInstance, bindingContext.getIndex().getAnnotations( SQL_RESULT_SET_MAPPINGS );
"value", for ( final AnnotationInstance sqlResultSetMappingsAnnotationInstance : sqlResultSetMappingsAnnotations ) {
AnnotationInstance[].class, final AnnotationInstance[] sqlResultSetMappingAnnotations = JandexHelper.extractAnnotationsValue(
bindingContext.getBuildingOptions().getServiceRegistry().getService( ClassLoaderService.class ) sqlResultSetMappingsAnnotationInstance,
) ) { "value"
bindSqlResultSetMapping( bindingContext, annotationInstance ); );
for ( AnnotationInstance sqlResultSetMappingAnnotation : sqlResultSetMappingAnnotations ) {
bindSqlResultSetMapping( sqlResultSetMappingAnnotation, bindingContext );
}
} }
} }
} }
private static int entityAliasIndex = 0; private static int entityAliasIndex = 0;
private static void bindSqlResultSetMapping(final AnnotationBindingContext bindingContext, final AnnotationInstance annotation) { private static void bindSqlResultSetMapping(AnnotationInstance annotation, AnnotationBindingContext bindingContext) {
final ClassLoaderService classLoaderService = bindingContext.getBuildingOptions().getServiceRegistry().getService( ClassLoaderService.class );
entityAliasIndex = 0; entityAliasIndex = 0;
final String name = JandexHelper.getValue( annotation, "name", String.class, classLoaderService );
// `name` is required...
final String name = annotation.value( "name" ).asString();
LOG.debugf( "Binding @SqlResultSetMapping(name=%s)", name ); LOG.debugf( "Binding @SqlResultSetMapping(name=%s)", name );
final ResultSetMappingDefinition definition = new ResultSetMappingDefinition( name ); final ResultSetMappingDefinition definition = new ResultSetMappingDefinition( name );
for ( final AnnotationInstance entityResult : JandexHelper.getValue(
final AnnotationInstance[] entityResults = JandexHelper.extractAnnotationsValue(
annotation, annotation,
"entities", "entities"
AnnotationInstance[].class, );
classLoaderService if ( entityResults != null && entityResults.length > 0 ) {
) ) { for ( AnnotationInstance entityResult : entityResults ) {
bindEntityResult( bindingContext, entityResult, definition ); bindEntityResult( entityResult, definition, bindingContext );
}
} }
for ( final AnnotationInstance columnResult : JandexHelper.getValue(
final AnnotationInstance[] columnResults = JandexHelper.extractAnnotationsValue(
annotation, annotation,
"columns", "columns"
AnnotationInstance[].class, );
classLoaderService if ( columnResults != null && columnResults.length > 0 ) {
) ) { for ( AnnotationInstance columnResult : columnResults ) {
bindColumnResult( bindingContext, columnResult, definition ); bindColumnResult( columnResult, definition, bindingContext );
}
}
final AnnotationInstance[] constructorResults = JandexHelper.extractAnnotationsValue(
annotation,
"classes"
);
if ( constructorResults != null && constructorResults.length > 0 ) {
for ( AnnotationInstance constructorResult : constructorResults ) {
bindConstructorResult( constructorResult, definition, bindingContext );
}
} }
bindingContext.getMetadataCollector().addResultSetMapping( definition ); bindingContext.getMetadataCollector().addResultSetMapping( definition );
} }
private static void bindEntityResult( private static void bindEntityResult(
final AnnotationBindingContext bindingContext,
final AnnotationInstance entityResult, final AnnotationInstance entityResult,
final ResultSetMappingDefinition definition) { final ResultSetMappingDefinition definition,
final ClassLoaderService classLoaderService = bindingContext.getBuildingOptions().getServiceRegistry().getService( ClassLoaderService.class ); final AnnotationBindingContext bindingContext) {
final String className = JandexHelper.getValue( entityResult, "entityClass", String.class, classLoaderService ); final String className = entityResult.value( "entityClass" ).asString();
final EntityBinding targetEntityBinding = bindingContext.getMetadataCollector().getEntityBinding( className ); final EntityBinding targetEntityBinding = bindingContext.getMetadataCollector().getEntityBinding( className );
if ( targetEntityBinding == null ) { if ( targetEntityBinding == null ) {
throw new MappingException( throw new MappingException(
String.format( String.format(
"Entity[%s] not found in SqlResultMapping[%s]", "Entity [%s] not found in SqlResultMapping [%s]",
className, className,
definition.getName() definition.getName()
) )
); );
} }
final String discriminatorColumn = JandexHelper.getValue( entityResult, "discriminatorColumn", String.class,
classLoaderService );
final Map<String, String[]> propertyResults = new HashMap<String, String[]>(); final Map<String, String[]> propertyResults = new HashMap<String, String[]>();
final AnnotationValue discriminatorColumnValue = entityResult.value( "discriminatorColumn" );
if ( discriminatorColumnValue != null ) {
final String discriminatorColumn = discriminatorColumnValue.asString();
if ( StringHelper.isNotEmpty( discriminatorColumn ) ) {
propertyResults.put(
"class",
new String[] { normalize( discriminatorColumn, bindingContext ) }
);
if ( StringHelper.isNotEmpty( discriminatorColumn ) ) { }
final String quotingNormalizedName = bindingContext.getMetadataCollector()
.getObjectNameNormalizer()
.normalizeIdentifierQuoting( discriminatorColumn );
propertyResults.put( "class", new String[] { quotingNormalizedName } );
} }
List<FieldResult> fieldResultList = reorderFieldResult( List<FieldResult> fieldResultList = reorderFieldResult(
bindingContext, bindingContext,
entityResult, entityResult,
@ -171,24 +211,29 @@ private static void bindEntityResult(
definition.addQueryReturn( result ); definition.addQueryReturn( result );
} }
private static String normalize(String name, AnnotationBindingContext bindingContext) {
return bindingContext.getMetadataCollector()
.getObjectNameNormalizer()
.normalizeIdentifierQuoting( name );
}
//todo see org.hibernate.cfg.annotations.ResultsetMappingSecondPass#getSubPropertyIterator //todo see org.hibernate.cfg.annotations.ResultsetMappingSecondPass#getSubPropertyIterator
private static List<FieldResult> reorderFieldResult( private static List<FieldResult> reorderFieldResult(
AnnotationBindingContext bindingContext, AnnotationBindingContext bindingContext,
AnnotationInstance entityResult, AnnotationInstance entityResult,
EntityBinding entityBinding, EntityBinding entityBinding,
String resultSetMappingDefinitionName) { String resultSetMappingDefinitionName) {
final ClassLoaderService classLoaderService = bindingContext.getBuildingOptions().getServiceRegistry().getService( ClassLoaderService.class ); final AnnotationInstance[] fieldResultAnnotationInstances = JandexHelper.extractAnnotationsValue(
final AnnotationInstance[] fieldResultAnnotationInstances = JandexHelper.getValue(
entityResult, entityResult,
"fields", "fields"
AnnotationInstance[].class,
classLoaderService
); );
List<FieldResult> results = new ArrayList<FieldResult>( fieldResultAnnotationInstances.length );
List<String> propertyNames = new ArrayList<String>(); final List<FieldResult> results = new ArrayList<FieldResult>( fieldResultAnnotationInstances.length );
final List<String> propertyNames = new ArrayList<String>();
final Set<String> uniqueReturnProperty = new HashSet<String>(); final Set<String> uniqueReturnProperty = new HashSet<String>();
for ( final AnnotationInstance fieldResult : fieldResultAnnotationInstances ) { for ( final AnnotationInstance fieldResult : fieldResultAnnotationInstances ) {
final String name = JandexHelper.getValue( fieldResult, "name", String.class, classLoaderService ); final String name = fieldResult.value( "name" ).asString();
if ( !uniqueReturnProperty.add( name ) ) { if ( !uniqueReturnProperty.add( name ) ) {
throw new MappingException( throw new MappingException(
"duplicate @FieldResult for property " + name "duplicate @FieldResult for property " + name
@ -198,12 +243,13 @@ private static List<FieldResult> reorderFieldResult(
} }
if ( "class".equals( name ) ) { if ( "class".equals( name ) ) {
throw new MappingException( throw new MappingException(
"class is not a valid property name to use in a @FieldResult, use @EntityResult(discriminatorColumn) instead" "class is not a valid property name to use in a @FieldResult, " +
"use @EntityResult(discriminatorColumn) instead"
); );
} }
final String column = JandexHelper.getValue( fieldResult, "column", String.class,
classLoaderService ); final String column = fieldResult.value( "column" ).asString();
final String quotingNormalizedColumnName = normalize( bindingContext, column ); final String quotingNormalizedColumnName = normalize( column, bindingContext );
if ( name.contains( "." ) ) { if ( name.contains( "." ) ) {
int dotIndex = name.lastIndexOf( '.' ); int dotIndex = name.lastIndexOf( '.' );
String reducedName = name.substring( 0, dotIndex ); String reducedName = name.substring( 0, dotIndex );
@ -326,19 +372,44 @@ private static void insert(String key, String value, Map<String, String[]> map)
} }
} }
private static void bindColumnResult(final AnnotationBindingContext bindingContext, private static void bindColumnResult(
final AnnotationInstance columnResult, AnnotationInstance columnResult,
final ResultSetMappingDefinition definition) { ResultSetMappingDefinition definition,
final String name = JandexHelper.getValue( columnResult, "name", String.class, AnnotationBindingContext bindingContext) {
bindingContext.getBuildingOptions().getServiceRegistry().getService( ClassLoaderService.class ) ); definition.addQueryReturn( extractColumnResult( columnResult, bindingContext ) );
final String normalizedName = normalize( bindingContext, name );
//todo TYPE
definition.addQueryReturn( new NativeSQLQueryScalarReturn( normalizedName, null ) );
} }
private static String normalize(final AnnotationBindingContext bindingContext, String name) { private static NativeSQLQueryScalarReturn extractColumnResult(
return bindingContext.getMetadataCollector() AnnotationInstance columnResult,
.getObjectNameNormalizer() AnnotationBindingContext bindingContext) {
.normalizeIdentifierQuoting( name ); // `name` is required
final String name = columnResult.value( "name" ).asString();
final String normalizedName = normalize( name, bindingContext );
//todo TYPE
return new NativeSQLQueryScalarReturn( normalizedName, null );
}
private static void bindConstructorResult(
AnnotationInstance constructorResult,
ResultSetMappingDefinition definition,
AnnotationBindingContext bindingContext) {
final Class classToConstruct = bindingContext.getServiceRegistry()
.getService( ClassLoaderService.class )
.classForName( constructorResult.value( "targetClass" ).asString() );
final List<NativeSQLQueryScalarReturn> columns = new ArrayList<NativeSQLQueryScalarReturn>();
final AnnotationInstance[] columnResults = JandexHelper.extractAnnotationsValue(
constructorResult,
"columns"
);
if ( columnResults != null && columnResults.length > 0 ) {
for ( AnnotationInstance columnResult : columnResults ) {
columns.add( extractColumnResult( columnResult, bindingContext ) );
}
}
definition.addQueryReturn(
new NativeSQLQueryConstructorReturn( classToConstruct, columns )
);
} }
} }

View File

@ -49,7 +49,6 @@
/** /**
* @author Steve Ebersole * @author Steve Ebersole
*/ */
@FailureExpectedWithNewMetamodel( jiraKey = "HHH-9060" )
public class ConstructorResultNativeQueryTest extends BaseEntityManagerFunctionalTestCase { public class ConstructorResultNativeQueryTest extends BaseEntityManagerFunctionalTestCase {
@Entity( name = "Person" ) @Entity( name = "Person" )
@SqlResultSetMappings( @SqlResultSetMappings(