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

org.hibernate.mapping.BasicValue Maven / Gradle / Ivy

There is a newer version: 7.0.0.Beta1
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 http://www.gnu.org/licenses/lgpl-2.1.html
 */
package org.hibernate.mapping;

import java.sql.Types;
import java.util.Properties;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.persistence.EnumType;
import javax.persistence.TemporalType;

import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.boot.model.TypeDefinition;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.convert.spi.JpaAttributeConverterCreationContext;
import org.hibernate.boot.model.process.internal.EnumeratedValueResolution;
import org.hibernate.boot.model.process.internal.InferredBasicValueResolution;
import org.hibernate.boot.model.process.internal.InferredBasicValueResolver;
import org.hibernate.boot.model.process.internal.NamedBasicTypeResolution;
import org.hibernate.boot.model.process.internal.NamedConverterResolution;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.model.convert.internal.NamedEnumValueConverter;
import org.hibernate.metamodel.model.convert.internal.OrdinalEnumValueConverter;
import org.hibernate.metamodel.model.convert.spi.BasicValueConverter;
import org.hibernate.metamodel.model.convert.spi.EnumValueConverter;
import org.hibernate.resource.beans.spi.ManagedBeanRegistry;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.BasicType;
import org.hibernate.type.CustomType;
import org.hibernate.type.RowVersionType;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.java.EnumJavaTypeDescriptor;
import org.hibernate.type.descriptor.java.ImmutableMutabilityPlan;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.java.MutabilityPlan;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor;
import org.hibernate.type.descriptor.java.RowVersionTypeDescriptor;
import org.hibernate.type.descriptor.java.TemporalJavaTypeDescriptor;
import org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators;
import org.hibernate.type.descriptor.sql.spi.SqlTypeDescriptorRegistry;
import org.hibernate.type.spi.TypeConfiguration;

/**
 * @author Steve Ebersole
 */
public class BasicValue extends SimpleValue implements SqlTypeDescriptorIndicators {

	private final TypeConfiguration typeConfiguration;
	private final int preferredJdbcTypeCodeForBoolean;


	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// incoming "configuration" values


	private Function> explicitJavaTypeAccess;
	private Function explicitSqlTypeAccess;

	private MutabilityPlan explicitMutabilityPlan;

	private boolean isNationalized;
	private boolean isLob;
	private EnumType enumerationStyle;
	private TemporalType temporalPrecision;

	private ConverterDescriptor attributeConverterDescriptor;

	private Class resolvedJavaClass;

	private String ownerName;
	private String propertyName;

	private Properties explicitLocalTypeParams;

	private BasicValue dependentValue;


	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Resolved state - available after `#setTypeUsingReflection`

	private Resolution resolution;

	public BasicValue(MetadataBuildingContext buildingContext) {
		super( buildingContext );

		this.typeConfiguration = buildingContext.getBootstrapContext().getTypeConfiguration();
		this.preferredJdbcTypeCodeForBoolean = buildingContext.getPreferredSqlTypeCodeForBoolean();
	}

	public BasicValue(MetadataBuildingContext buildingContext, Table table) {
		super( buildingContext, table );

		this.typeConfiguration = buildingContext.getBootstrapContext().getTypeConfiguration();
		this.preferredJdbcTypeCodeForBoolean = buildingContext.getPreferredSqlTypeCodeForBoolean();
	}


	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Setters - in preparation of resolution

	public void setJavaClass(Class resolvedJavaClass) {
		this.resolvedJavaClass = resolvedJavaClass;
	}

	@Override
	public void setTypeUsingReflection(String className, String propertyName) throws MappingException {
		if ( resolution != null ) {
			throw new IllegalStateException( "BasicValue already resolved" );
		}

		this.ownerName = className;
		this.propertyName = propertyName;
	}

	public void setEnumerationStyle(EnumType enumerationStyle, String enumJavaTypeName) {
		this.enumerationStyle = enumerationStyle;
		this.resolvedJavaClass = getBuildingContext().getBootstrapContext()
				.getServiceRegistry()
				.getService( ClassLoaderService.class )
				.classForName( enumJavaTypeName );
	}

	@SuppressWarnings("WeakerAccess")
	public EnumType getEnumerationStyle() {
		return enumerationStyle;
	}

	@Override
	public ConverterDescriptor getJpaAttributeConverterDescriptor() {
		return attributeConverterDescriptor;
	}

	@Override
	public void setJpaAttributeConverterDescriptor(ConverterDescriptor descriptor) {
		this.attributeConverterDescriptor = descriptor;
	}


	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Resolution

	@Override
	public Type getType() throws MappingException {
		resolve();
		assert resolution != null;

		return resolution.getResolvedBasicType();
	}

	public Resolution resolve() {
		if ( resolution != null ) {
			return resolution;
		}

		final String explicitTypeName = getTypeName();
		if ( explicitTypeName != null ) {
			resolution = interpretExplicitlyNamedType(
					explicitTypeName,
					enumerationStyle,
					resolvedJavaClass,
					explicitJavaTypeAccess,
					explicitSqlTypeAccess,
					attributeConverterDescriptor,
					explicitMutabilityPlan,
					explicitLocalTypeParams,
					this,
					typeConfiguration,
					getBuildingContext()
			);
		}
		else {
			final ServiceRegistry serviceRegistry = typeConfiguration.getServiceRegistry();
			final ClassLoaderService classLoaderService = serviceRegistry.getService( ClassLoaderService.class );

			resolution = implicitlyResolveBasicType(
					explicitJavaTypeAccess,
					explicitSqlTypeAccess,
					attributeConverterDescriptor,
					this,
					() -> {
						if ( resolvedJavaClass != null ) {
							//noinspection unchecked
							return getBuildingContext().getBootstrapContext()
									.getTypeConfiguration()
									.getJavaTypeDescriptorRegistry()
									.resolveDescriptor( resolvedJavaClass );
						}
						else if ( ownerName != null && propertyName != null ) {
							final Class reflectedJavaType = ReflectHelper.reflectedPropertyClass(
									ownerName,
									propertyName,
									classLoaderService
							);

							// First resolve from the BasicTypeRegistry.
							// If it does resolve, we can use the JTD instead of delegating to the JTD Regsitry.
							final BasicType basicType = getBuildingContext().getBootstrapContext()
									.getTypeConfiguration()
									.getBasicTypeRegistry()
									.getRegisteredType( reflectedJavaType.getName() );
							if ( basicType != null ) {
								return basicType.getJavaTypeDescriptor();
							}

							//noinspection unchecked
							return getBuildingContext().getBootstrapContext()
									.getTypeConfiguration()
									.getJavaTypeDescriptorRegistry()
									.resolveDescriptor( reflectedJavaType );
						}
						else if ( dependentValue != null ) {
							// todo (6.0) - Can we just use the resolution directly?
							//		In 5.x we copied the typeName and its associated parameters for this use case.
							//		It would stand to reason we could just share the same Resolution instance
							//		instead of constructing this BasicValue's very own here?
							return dependentValue.resolve().getDomainJavaDescriptor();
						}

						return null;
					},
					isVersion(),
					typeConfiguration
			);
		}

		return resolution;
	}



	@SuppressWarnings("unchecked")
	private static Resolution interpretExplicitlyNamedType(
			String name,
			EnumType enumerationStyle,
			Class resolvedJavaClass,
			Function> explicitJtdAccess,
			Function explicitStdAccess,
			ConverterDescriptor converterDescriptor,
			MutabilityPlan explicitMutabilityPlan,
			Properties localTypeParams,
			SqlTypeDescriptorIndicators stdIndicators,
			TypeConfiguration typeConfiguration,
			MetadataBuildingContext context) {

		final ManagedBeanRegistry managedBeanRegistry = context.getBootstrapContext()
				.getServiceRegistry()
				.getService( ManagedBeanRegistry.class );

		final JpaAttributeConverterCreationContext converterCreationContext = new JpaAttributeConverterCreationContext() {
			@Override
			public ManagedBeanRegistry getManagedBeanRegistry() {
				return managedBeanRegistry;
			}

			@Override
			public TypeConfiguration getTypeConfiguration() {
				return typeConfiguration;
			}
		};


		// Name could refer to:
		//		1) a named converter - HBM support for JPA's AttributeConverter via its `type="..."` XML attribute
		//		2) basic type "resolution key"
		//		3) UserType or BasicType class name - directly, or through a TypeDefinition

		if ( name.startsWith( ConverterDescriptor.TYPE_NAME_PREFIX  ) ) {
			// atm this should never happen due to impl of `#setExplicitTypeName`
			return NamedConverterResolution.from(
					name,
					explicitJtdAccess,
					explicitStdAccess,
					converterCreationContext,
					explicitMutabilityPlan,
					stdIndicators,
					context
			);
		}

		if ( enumerationStyle != null ) {
			assert resolvedJavaClass != null;
			assert resolvedJavaClass.isEnum();

			final JavaTypeDescriptorRegistry jtdRegistry = typeConfiguration.getJavaTypeDescriptorRegistry();
			final SqlTypeDescriptorRegistry stdRegistry = typeConfiguration.getSqlTypeDescriptorRegistry();

			final EnumJavaTypeDescriptor domainJtd = (EnumJavaTypeDescriptor) jtdRegistry.getDescriptor( resolvedJavaClass );

			final JavaTypeDescriptor jdbcJtd;
			final SqlTypeDescriptor std;
			final EnumValueConverter valueConverter;

			if ( enumerationStyle == EnumType.ORDINAL ) {
				jdbcJtd = jtdRegistry.getDescriptor( Integer.class );
				final SqlTypeDescriptor explicitStd = explicitStdAccess == null ? null : explicitStdAccess.apply( typeConfiguration );
				std = explicitStd != null ? explicitStd : stdRegistry.getDescriptor( Types.INTEGER );
				valueConverter = new OrdinalEnumValueConverter( domainJtd, std, jdbcJtd );
			}
			else {
				jdbcJtd = jtdRegistry.getDescriptor( String.class );
				std = jdbcJtd.getJdbcRecommendedSqlType( stdIndicators );
				valueConverter = new NamedEnumValueConverter( domainJtd, std, jdbcJtd );
			}

			final CustomType valueMapper = new CustomType(
					new org.hibernate.type.EnumType( resolvedJavaClass, valueConverter, typeConfiguration ),
					typeConfiguration
			);

			return new EnumeratedValueResolution(
					valueMapper,
					domainJtd,
					jdbcJtd,
					std,
					valueConverter
			);
		}

		// see if it is a named basic type
		final BasicType basicTypeByName = typeConfiguration.getBasicTypeRegistry().getRegisteredType( name );
		if ( basicTypeByName != null ) {
			final BasicValueConverter valueConverter;
			final JavaTypeDescriptor domainJtd;
			if ( converterDescriptor == null ) {
				valueConverter = null;
				domainJtd = basicTypeByName.getJavaTypeDescriptor();
			}
			else {
				valueConverter = converterDescriptor.createJpaAttributeConverter( converterCreationContext );
				domainJtd = valueConverter.getDomainJavaDescriptor();
			}

			return new NamedBasicTypeResolution(
					domainJtd,
					basicTypeByName,
					valueConverter,
					explicitMutabilityPlan,
					context
			);
		}

		// see if it is a named TypeDefinition
		final TypeDefinition typeDefinition = context.getTypeDefinitionRegistry().resolve( name );
		if ( typeDefinition != null ) {
			return typeDefinition.resolve(
					explicitJtdAccess.apply( typeConfiguration ),
					explicitStdAccess.apply( typeConfiguration ),
					localTypeParams,
					explicitMutabilityPlan,
					context
			);
		}


		// see if the name is a UserType or BasicType implementor class name
		final ClassLoaderService cls = typeConfiguration.getServiceRegistry().getService( ClassLoaderService.class );
		try {
			final Class typeNamedClass = cls.classForName( name );

			// if there are no local config params, register an implicit TypeDefinition for this custom type .
			//  later uses may find it and re-use its cacheable reference...
			if ( CollectionHelper.isEmpty( localTypeParams ) ) {
				final TypeDefinition implicitDefinition = new TypeDefinition(
						name,
						typeNamedClass,
						null,
						null,
						typeConfiguration
				);
				context.getTypeDefinitionRegistry().register( implicitDefinition );
				return implicitDefinition.resolve(
						explicitJtdAccess != null ? explicitJtdAccess.apply( typeConfiguration ) : null,
						explicitStdAccess != null ? explicitStdAccess.apply( typeConfiguration ) : null,
						null,
						explicitMutabilityPlan,
						context
				);
			}

			return TypeDefinition.createLocalResolution(
					name,
					typeNamedClass,
					explicitJtdAccess.apply( typeConfiguration ),
					explicitStdAccess.apply( typeConfiguration ),
					explicitMutabilityPlan,
					localTypeParams,
					context
			);
		}
		catch (ClassLoadingException ignore) {
			// allow the exception below to trigger
		}

		throw new MappingException( "Could not resolve named type : " + name );
	}

	@SuppressWarnings("unchecked")
	private static Resolution implicitlyResolveBasicType(
			Function> explicitJtdAccess,
			Function explicitStdAccess,
			ConverterDescriptor attributeConverterDescriptor,
			SqlTypeDescriptorIndicators stdIndicators,
			Supplier reflectedJtdResolver,
			boolean isVersion,
			TypeConfiguration typeConfiguration) {

		final InferredBasicValueResolver resolver = new InferredBasicValueResolver( explicitJtdAccess, explicitStdAccess, typeConfiguration );

		if ( attributeConverterDescriptor != null ) {
			assert !isVersion : "Version attribute cannot define AttributeConverter";

			// we have an attribute converter, use that to either:
			//		1) validate the explicit BasicJavaDescriptor/SqlTypeDescriptor
			//		2) use the converter Class parameters to infer the BasicJavaDescriptor/SqlTypeDescriptor

			final Class converterDomainJavaType = attributeConverterDescriptor.getDomainValueResolvedType()
					.getErasedType();

			final JavaTypeDescriptor converterDomainJtd = typeConfiguration.getJavaTypeDescriptorRegistry()
					.getDescriptor( converterDomainJavaType );

			if ( resolver.getDomainJtd() == null ) {
				resolver.setDomainJtd( converterDomainJtd );
			}
			else {
				if ( !resolver.getDomainJtd().equals( converterDomainJtd ) ) {
					throw new HibernateException(
							"JavaTypeDescriptors did not match between BasicTypeParameters#getJavaTypeDescriptor and " +
									"BasicTypeParameters#getAttributeConverterDefinition#getDomainType"
					);
				}
			}

			final Class converterRelationalJavaType = attributeConverterDescriptor.getRelationalValueResolvedType()
					.getErasedType();

			resolver.setRelationalJtd(
					typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( converterRelationalJavaType )
			);

			final SqlTypeDescriptor converterHintedStd = resolver.getRelationalJtd().getJdbcRecommendedSqlType( stdIndicators );

			if ( resolver.getRelationalStd() == null ) {
				resolver.setRelationalStd( converterHintedStd );
			}
			else {
				if ( !resolver.getRelationalStd().equals( converterHintedStd ) ) {
					throw new HibernateException(
							"SqlTypeDescriptors did not match between BasicTypeParameters#getSqlTypeDescriptor and " +
									"BasicTypeParameters#getAttributeConverterDefinition#getJdbcType"
					);
				}
			}

			resolver.setValueConverter(
					attributeConverterDescriptor.createJpaAttributeConverter(
							new JpaAttributeConverterCreationContext() {
								@Override
								public ManagedBeanRegistry getManagedBeanRegistry() {
									return typeConfiguration.getServiceRegistry().getService( ManagedBeanRegistry.class );
								}

								@Override
								public TypeConfiguration getTypeConfiguration() {
									return typeConfiguration;
								}
							}
					)
			);
		}
		else {
			if ( resolver.getDomainJtd() == null ) {
				resolver.setDomainJtd( reflectedJtdResolver.get() );
			}

			if ( resolver.getDomainJtd() instanceof EnumJavaTypeDescriptor ) {
				final EnumJavaTypeDescriptor enumJavaDescriptor = (EnumJavaTypeDescriptor) resolver.getDomainJtd();

				final EnumType enumType = stdIndicators.getEnumeratedType() != null
						? stdIndicators.getEnumeratedType()
						: EnumType.ORDINAL;

				switch ( enumType ) {
					case STRING: {
						final JavaTypeDescriptor stringJtd = typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( String.class );
						final SqlTypeDescriptor relationalStd = stringJtd.getJdbcRecommendedSqlType( stdIndicators );

						resolver.setRelationalJtd( stringJtd );
						resolver.setRelationalStd( relationalStd );

						final NamedEnumValueConverter valueConverter = new NamedEnumValueConverter(
								enumJavaDescriptor,
								relationalStd,
								stringJtd
						);

						resolver.setValueConverter( valueConverter );

						final org.hibernate.type.EnumType enumMappingType = new org.hibernate.type.EnumType(
								enumJavaDescriptor.getJavaType(),
								valueConverter,
								typeConfiguration
						);

						final CustomType basicType = new CustomType( enumMappingType, typeConfiguration );

						resolver.injectResolution(
								new InferredBasicValueResolution(
										basicType,
										enumJavaDescriptor,
										stringJtd,
										relationalStd,
										valueConverter,
										ImmutableMutabilityPlan.INSTANCE
								)
						);
						break;
					}
					case ORDINAL: {
						final JavaTypeDescriptor integerJtd = typeConfiguration.getJavaTypeDescriptorRegistry().getDescriptor( Integer.class );
						final SqlTypeDescriptor std = integerJtd.getJdbcRecommendedSqlType( stdIndicators );

						resolver.setRelationalJtd( integerJtd );
						resolver.setRelationalStd( std );

						final OrdinalEnumValueConverter valueConverter = new OrdinalEnumValueConverter(
								enumJavaDescriptor,
								std,
								integerJtd
						);

						resolver.setValueConverter( valueConverter );

						final org.hibernate.type.EnumType enumMappingType = new org.hibernate.type.EnumType(
								enumJavaDescriptor.getJavaType(),
								valueConverter,
								typeConfiguration
						);

						final CustomType basicType = new CustomType( enumMappingType, typeConfiguration );

						resolver.injectResolution(
								new InferredBasicValueResolution(
										basicType,
										enumJavaDescriptor,
										integerJtd,
										std,
										valueConverter,
										ImmutableMutabilityPlan.INSTANCE
								)
						);
						break;
					}
					default: {
						throw new HibernateException( "Unknown EnumType : " + enumType );
					}
				}
			}
			else if ( resolver.getDomainJtd() instanceof TemporalJavaTypeDescriptor ) {
				if ( stdIndicators.getTemporalPrecision() != null ) {
					resolver.setDomainJtd(
							( (TemporalJavaTypeDescriptor) resolver.getDomainJtd() ).resolveTypeForPrecision(
									stdIndicators.getTemporalPrecision(),
									typeConfiguration
							)
					);
				}
			}
			else if ( resolver.getDomainJtd() instanceof PrimitiveByteArrayTypeDescriptor && isVersion ) {
				resolver.setDomainJtd( RowVersionTypeDescriptor.INSTANCE );
				resolver.injectResolution(
						new InferredBasicValueResolution(
								RowVersionType.INSTANCE,
								RowVersionType.INSTANCE.getJavaTypeDescriptor(),
								RowVersionType.INSTANCE.getJavaTypeDescriptor(),
								RowVersionType.INSTANCE.getSqlTypeDescriptor(),
								null,
								ImmutableMutabilityPlan.INSTANCE
						)
				);
			}
		}


		if ( resolver.getRelationalStd() == null ) {
			if ( resolver.getRelationalJtd() == null ) {
				if ( resolver.getDomainJtd() == null ) {
					throw new IllegalArgumentException(
							"Could not determine JavaTypeDescriptor nor SqlTypeDescriptor to use"
					);
				}

				resolver.setRelationalJtd( resolver.getDomainJtd() );
			}

			resolver.setRelationalStd( resolver.getRelationalJtd().getJdbcRecommendedSqlType( stdIndicators ) );
		}
		else if ( resolver.getRelationalJtd() == null ) {
			resolver.setRelationalJtd(
					resolver.getRelationalStd().getJdbcRecommendedJavaTypeMapping( typeConfiguration )
			);
			if ( resolver.getDomainJtd() == null ) {
				resolver.setDomainJtd( resolver.getRelationalJtd() );
			}
		}

		return resolver.build();
	}


	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// SqlTypeDescriptorIndicators

	@Override
	public EnumType getEnumeratedType() {
		return getEnumerationStyle();
	}

	public boolean isNationalized() {
		return isNationalized;
	}

	public boolean isLob() {
		return isLob;
	}

	@Override
	public int getPreferredSqlTypeCodeForBoolean() {
		return preferredJdbcTypeCodeForBoolean;
	}

	@Override
	public TypeConfiguration getTypeConfiguration() {
		return typeConfiguration;
	}

	public void setExplicitTypeParams(Properties explicitLocalTypeParams) {
		this.explicitLocalTypeParams = explicitLocalTypeParams;
	}

	/**
	 * Resolved form of {@link BasicValue} as part of interpreting the
	 * boot-time model into the run-time model
	 */
	public interface Resolution {
		/**
		 * The associated BasicType
		 */
		BasicType getResolvedBasicType();

		/**
		 * The JavaTypeDescriptor for the value as part of the domain model
		 */
		JavaTypeDescriptor getDomainJavaDescriptor();

		/**
		 * The JavaTypeDescriptor for the relational value as part of
		 * the relational model (its JDBC representation)
		 */
		JavaTypeDescriptor getRelationalJavaDescriptor();

		/**
		 * The JavaTypeDescriptor for the relational value as part of
		 * the relational model (its JDBC representation)
		 */
		SqlTypeDescriptor getRelationalSqlTypeDescriptor();

		/**
		 * Converter, if any, to convert values between the
		 * domain and relational JavaTypeDescriptor representations
		 */
		BasicValueConverter getValueConverter();

		/**
		 * The resolved MutabilityPlan
		 */
		MutabilityPlan getMutabilityPlan();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy