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

org.hibernate.boot.model.source.internal.hbm.ResultSetMappingBinder Maven / Gradle / Ivy

There is a newer version: 6.5.0.CR2
Show newest version
/*
 * 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 depends 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy