org.hibernate.boot.model.source.internal.hbm.ResultSetMappingBinder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core Show documentation
Show all versions of hibernate-core Show documentation
Hibernate's core ORM functionality
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or .
*/
package org.hibernate.boot.model.source.internal.hbm;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.hibernate.AssertionFailure;
import org.hibernate.boot.MappingException;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmNativeQueryCollectionLoadReturnType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmNativeQueryJoinReturnType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmNativeQueryPropertyReturnType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmNativeQueryReturnType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmNativeQueryScalarReturnType;
import org.hibernate.boot.jaxb.hbm.spi.NativeQueryNonScalarRootReturn;
import org.hibernate.boot.jaxb.hbm.spi.ResultSetMappingBindingDefinition;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryCollectionReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryJoinReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn;
import org.hibernate.engine.query.spi.sql.NativeSQLQueryScalarReturn;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
import org.hibernate.type.Type;
/**
* @author Steve Ebersole
* @author Emmanuel Bernard
*/
public abstract class ResultSetMappingBinder {
/**
* Build a ResultSetMappingDefinition given a containing element for the "return-XXX" elements.
*
* This form is used for ResultSet mappings defined outside the context of any specific entity.
* For {@code hbm.xml} this means at the root of the document. For annotations, this means at
* the package level.
*
* @param resultSetMappingSource The XML data as a JAXB binding
* @param context The mapping state
*
* @return The ResultSet mapping descriptor
*/
public static ResultSetMappingDefinition bind(
ResultSetMappingBindingDefinition resultSetMappingSource,
HbmLocalMetadataBuildingContext context) {
if ( resultSetMappingSource.getName() == null ) {
throw new MappingException(
"ResultSet mapping did not specify name",
context.getOrigin()
);
}
final ResultSetMappingDefinition binding = new ResultSetMappingDefinition( resultSetMappingSource.getName() );
bind( resultSetMappingSource, binding, context );
return binding;
}
/**
* Build a ResultSetMappingDefinition given a containing element for the "return-XXX" elements
*
* @param resultSetMappingSource The XML data as a JAXB binding
* @param context The mapping state
* @param prefix A prefix to apply to named ResultSet mapping; this is either {@code null} for
* ResultSet mappings defined outside of any entity, or the name of the containing entity
* if defined within the context of an entity
*
* @return The ResultSet mapping descriptor
*/
public static ResultSetMappingDefinition bind(
ResultSetMappingBindingDefinition resultSetMappingSource,
HbmLocalMetadataBuildingContext context,
String prefix) {
if ( StringHelper.isEmpty( prefix ) ) {
throw new AssertionFailure( "Passed prefix was null; perhaps you meant to call the alternate #bind form?" );
}
final String resultSetName = prefix + '.' + resultSetMappingSource.getName();
final ResultSetMappingDefinition binding = new ResultSetMappingDefinition( resultSetName );
bind( resultSetMappingSource, binding, context );
return binding;
}
private static void bind(
ResultSetMappingBindingDefinition resultSetMappingSource,
ResultSetMappingDefinition binding,
HbmLocalMetadataBuildingContext context) {
int cnt = 0;
for ( Object valueMappingSource : resultSetMappingSource.getValueMappingSources() ) {
if ( JaxbHbmNativeQueryReturnType.class.isInstance( valueMappingSource ) ) {
binding.addQueryReturn(
extractReturnDescription( (JaxbHbmNativeQueryReturnType) valueMappingSource, context, cnt++ )
);
}
else if ( JaxbHbmNativeQueryCollectionLoadReturnType.class.isInstance( valueMappingSource ) ) {
binding.addQueryReturn(
extractReturnDescription( (JaxbHbmNativeQueryCollectionLoadReturnType) valueMappingSource, context, cnt++ )
);
}
else if ( JaxbHbmNativeQueryJoinReturnType.class.isInstance( valueMappingSource ) ) {
binding.addQueryReturn(
extractReturnDescription( (JaxbHbmNativeQueryJoinReturnType) valueMappingSource, context, cnt++ )
);
}
else if ( JaxbHbmNativeQueryScalarReturnType.class.isInstance( valueMappingSource ) ) {
binding.addQueryReturn(
extractReturnDescription( (JaxbHbmNativeQueryScalarReturnType) valueMappingSource, context )
);
}
}
}
// todo : look to add query/resultset-mapping name to exception messages here.
// needs a kind of "context", i.e.:
// "Unable to resolve type [%s] specified for native query scalar return"
// becomes
// "Unable to resolve type [%s] specified for native query scalar return as part of [query|resultset-mapping] [name]"
//
// MappingException already carries origin, adding the query/resultset-mapping name pinpoints the location :)
public static NativeSQLQueryScalarReturn extractReturnDescription(
JaxbHbmNativeQueryScalarReturnType rtnSource,
HbmLocalMetadataBuildingContext context) {
final String column = rtnSource.getColumn();
final String typeName = rtnSource.getType();
Type type = null;
if ( typeName != null ) {
type = context.getMetadataCollector().getTypeResolver().heuristicType( typeName );
if ( type == null ) {
throw new MappingException(
String.format(
"Unable to resolve type [%s] specified for native query scalar return",
typeName
),
context.getOrigin()
);
}
}
return new NativeSQLQueryScalarReturn( column, type );
}
public static NativeSQLQueryRootReturn extractReturnDescription(
JaxbHbmNativeQueryReturnType rtnSource,
HbmLocalMetadataBuildingContext context,
int queryReturnPosition) {
String alias = rtnSource.getAlias();
if ( StringHelper.isEmpty( alias ) ) {
// hack-around as sqlquery impl depend on having a key.
alias = "alias_" + queryReturnPosition;
}
final String entityName = context.determineEntityName(
rtnSource.getEntityName(),
rtnSource.getClazz()
);
final PersistentClass pc = context.getMetadataCollector().getEntityBinding( entityName );
return new NativeSQLQueryRootReturn(
alias,
entityName,
extractPropertyResults( alias, rtnSource, pc, context ),
rtnSource.getLockMode()
);
}
public static NativeSQLQueryJoinReturn extractReturnDescription(
JaxbHbmNativeQueryJoinReturnType rtnSource,
HbmLocalMetadataBuildingContext context,
int queryReturnPosition) {
final int dot = rtnSource.getProperty().lastIndexOf( '.' );
if ( dot == -1 ) {
throw new MappingException(
String.format(
Locale.ENGLISH,
"Role attribute for sql query return [%s] not formatted correctly {owningAlias.propertyName}",
rtnSource.getAlias()
),
context.getOrigin()
);
}
String roleOwnerAlias = rtnSource.getProperty().substring( 0, dot );
String roleProperty = rtnSource.getProperty().substring( dot + 1 );
return new NativeSQLQueryJoinReturn(
rtnSource.getAlias(),
roleOwnerAlias,
roleProperty,
//FIXME: get the PersistentClass
extractPropertyResults( rtnSource.getAlias(), rtnSource, null, context ),
rtnSource.getLockMode()
);
}
public static NativeSQLQueryReturn extractReturnDescription(
JaxbHbmNativeQueryCollectionLoadReturnType rtnSource,
HbmLocalMetadataBuildingContext context,
int queryReturnPosition) {
final int dot = rtnSource.getRole().lastIndexOf( '.' );
if ( dot == -1 ) {
throw new MappingException(
String.format(
Locale.ENGLISH,
"Collection attribute for sql query return [%s] not formatted correctly {OwnerClassName.propertyName}",
rtnSource.getAlias()
),
context.getOrigin()
);
}
String ownerClassName = context.findEntityBinding( null, rtnSource.getRole().substring( 0, dot ) )
.getClassName();
String ownerPropertyName = rtnSource.getRole().substring( dot + 1 );
return new NativeSQLQueryCollectionReturn(
rtnSource.getAlias(),
ownerClassName,
ownerPropertyName,
// FIXME: get the PersistentClass
extractPropertyResults( rtnSource.getAlias(), rtnSource, null, context ),
rtnSource.getLockMode()
);
}
/**
* return-property extraction/binding specific to an entity return to account for
* discriminator.
*
* @param alias The return alias
* @param rtnSource The entity return jaxb binding
* @param pc The located PersistentClass
* @param context The hbm context
*
* @return The extracted property mappings
*/
private static Map extractPropertyResults(
String alias,
JaxbHbmNativeQueryReturnType rtnSource,
PersistentClass pc,
HbmLocalMetadataBuildingContext context) {
Map results = extractPropertyResults(
alias,
(NativeQueryNonScalarRootReturn) rtnSource,
pc,
context
);
if ( rtnSource.getReturnDiscriminator() != null ) {
if ( results == null ) {
results = new HashMap();
}
final String column = rtnSource.getReturnDiscriminator().getColumn();
if ( column == null ) {
throw new MappingException(
String.format(
Locale.ENGLISH,
"return-discriminator [%s (%s)] did not specify column",
pc.getEntityName(),
alias
),
context.getOrigin()
);
}
results.put( "class", new String[] {column} );
}
return results;
}
@SuppressWarnings("unchecked")
private static Map extractPropertyResults(
String alias,
NativeQueryNonScalarRootReturn rtnSource,
PersistentClass pc,
HbmLocalMetadataBuildingContext context) {
if ( CollectionHelper.isEmpty( rtnSource.getReturnProperty() ) ) {
return null;
}
final HashMap results = new HashMap();
List propertyReturnSources = new ArrayList();
List propertyNames = new ArrayList();
for ( JaxbHbmNativeQueryPropertyReturnType propertyReturnSource : rtnSource.getReturnProperty() ) {
final int dotPosition = propertyReturnSource.getName().lastIndexOf( '.' );
if ( pc == null || dotPosition == -1 ) {
propertyReturnSources.add( propertyReturnSource );
propertyNames.add( propertyReturnSource.getName() );
}
else {
final String reducedName = propertyReturnSource.getName().substring( 0, dotPosition );
final Value value = pc.getRecursiveProperty( reducedName ).getValue();
Iterator parentPropItr;
if ( value instanceof Component ) {
final Component comp = (Component) value;
parentPropItr = comp.getPropertyIterator();
}
else if ( value instanceof ToOne ) {
final ToOne toOne = (ToOne) value;
final PersistentClass referencedPc = context.getMetadataCollector()
.getEntityBinding( toOne.getReferencedEntityName() );
if ( toOne.getReferencedPropertyName() != null ) {
try {
parentPropItr = ( (Component) referencedPc.getRecursiveProperty( toOne.getReferencedPropertyName() )
.getValue() ).getPropertyIterator();
}
catch (ClassCastException e) {
throw new MappingException(
"dotted notation reference neither a component nor a many/one to one",
e,
context.getOrigin()
);
}
}
else {
try {
if ( referencedPc.getIdentifierMapper() == null ) {
parentPropItr = ( (Component) referencedPc.getIdentifierProperty()
.getValue() ).getPropertyIterator();
}
else {
parentPropItr = referencedPc.getIdentifierMapper().getPropertyIterator();
}
}
catch (ClassCastException e) {
throw new MappingException(
"dotted notation reference neither a component nor a many/one to one",
e,
context.getOrigin()
);
}
}
}
else {
throw new MappingException(
"dotted notation reference neither a component nor a many/one to one",
context.getOrigin()
);
}
boolean hasFollowers = false;
List followers = new ArrayList();
while ( parentPropItr.hasNext() ) {
final Property parentProperty = (Property) parentPropItr.next();
final String currentPropertyName = parentProperty.getName();
final String currentName = reducedName + '.' + currentPropertyName;
if ( hasFollowers ) {
followers.add( currentName );
}
if ( propertyReturnSource.getName().equals( currentName ) ) {
hasFollowers = true;
}
}
int index = propertyNames.size();
for ( String follower : followers ) {
int currentIndex = getIndexOfFirstMatchingProperty( propertyNames, follower );
index = currentIndex != -1 && currentIndex < index ? currentIndex : index;
}
propertyNames.add( index, propertyReturnSource.getName() );
propertyReturnSources.add( index, propertyReturnSource );
}
}
Set uniqueReturnProperty = new HashSet();
for ( JaxbHbmNativeQueryPropertyReturnType propertyReturnBinding : propertyReturnSources ) {
final String name = propertyReturnBinding.getName();
if ( "class".equals( name ) ) {
throw new MappingException(
"class is not a valid property name to use in a , use instead",
context.getOrigin()
);
}
//TODO: validate existing of property with the chosen name. (secondpass )
ArrayList allResultColumns = extractResultColumns( propertyReturnBinding );
if ( allResultColumns.isEmpty() ) {
throw new MappingException(
String.format(
Locale.ENGLISH,
"return-property [alias=%s, property=%s] must specify at least one column or return-column name",
alias,
propertyReturnBinding.getName()
),
context.getOrigin()
);
}
if ( uniqueReturnProperty.contains( name ) ) {
throw new MappingException(
String.format(
Locale.ENGLISH,
"Duplicate return-property [alias=%s] : %s",
alias,
propertyReturnBinding.getName()
),
context.getOrigin()
);
}
uniqueReturnProperty.add( name );
// the issue here is that for representing an entity collection,
// the collection element values (the property values of the associated entity)
// are represented as 'element.{propertyname}'. Thus the StringHelper.root()
// here puts everything under 'element' (which additionally has significant
// meaning). Probably what we need to do is to something like this instead:
// String root = StringHelper.root( name );
// String key = root; // by default
// if ( !root.equals( name ) ) {
// // we had a dot
// if ( !root.equals( alias ) {
// // the root does not apply to the specific alias
// if ( "elements".equals( root ) {
// // we specifically have a representing an entity collection
// // and this is one of that entity's properties
// key = name;
// }
// }
// }
// but I am not clear enough on the intended purpose of this code block, especially
// in relation to the "Reorder properties" code block above...
// String key = StringHelper.root( name );
ArrayList intermediateResults = (ArrayList) results.get( name );
if ( intermediateResults == null ) {
results.put( name, allResultColumns );
}
else {
intermediateResults.addAll( allResultColumns );
}
}
for ( Object o : results.entrySet() ) {
Map.Entry entry = (Map.Entry) o;
if ( entry.getValue() instanceof ArrayList ) {
ArrayList list = (ArrayList) entry.getValue();
entry.setValue( list.toArray( new String[list.size()] ) );
}
}
return results.isEmpty() ? Collections.EMPTY_MAP : results;
}
private static int getIndexOfFirstMatchingProperty(List propertyNames, String follower) {
int propertySize = propertyNames.size();
for ( int propIndex = 0; propIndex < propertySize; propIndex++ ) {
if ( ( (String) propertyNames.get( propIndex ) ).startsWith( follower ) ) {
return propIndex;
}
}
return -1;
}
private static ArrayList extractResultColumns(JaxbHbmNativeQueryPropertyReturnType propertyReturnSource) {
final String column = unquote( propertyReturnSource.getColumn() );
ArrayList allResultColumns = new ArrayList();
if ( column != null ) {
allResultColumns.add( column );
}
for ( JaxbHbmNativeQueryPropertyReturnType.JaxbHbmReturnColumn returnColumnSource : propertyReturnSource.getReturnColumn() ) {
allResultColumns.add( unquote( returnColumnSource.getName() ) );
}
return allResultColumns;
}
private static String unquote(String name) {
if ( name != null && name.charAt( 0 ) == '`' ) {
name = name.substring( 1, name.length() - 1 );
}
return name;
}
}