org.hibernate.type.spi.TypeConfiguration 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.type.spi;
import java.io.InvalidObjectException;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Types;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.hibernate.HibernateException;
import org.hibernate.Incubating;
import org.hibernate.SessionFactory;
import org.hibernate.SessionFactoryObserver;
import org.hibernate.boot.cfgxml.spi.CfgXmlAccessService;
import org.hibernate.boot.spi.BasicTypeRegistration;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.uuid.LocalObjectUuidHelper;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.SessionFactoryRegistry;
import org.hibernate.metamodel.model.domain.internal.DomainMetamodelImpl;
import org.hibernate.metamodel.spi.DomainMetamodel;
import org.hibernate.query.BinaryArithmeticOperator;
import org.hibernate.query.internal.QueryHelper;
import org.hibernate.query.sqm.SqmExpressable;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.BasicType;
import org.hibernate.type.BasicTypeRegistry;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.descriptor.java.spi.JavaTypeDescriptorRegistry;
import org.hibernate.type.descriptor.sql.SqlTypeDescriptorIndicators;
import org.hibernate.type.descriptor.sql.spi.SqlTypeDescriptorRegistry;
import org.hibernate.type.internal.StandardBasicTypeImpl;
import org.hibernate.type.internal.TypeConfigurationRegistry;
import static org.hibernate.internal.CoreLogging.messageLogger;
import static org.hibernate.query.BinaryArithmeticOperator.DIVIDE;
/**
* Defines a set of available Type instances as isolated from other configurations. The
* isolation is defined by each instance of a TypeConfiguration.
*
* Note that each Type is inherently "scoped" to a TypeConfiguration. We only ever access
* a Type through a TypeConfiguration - specifically the TypeConfiguration in effect for
* the current persistence unit.
*
* Even though each Type instance is scoped to a TypeConfiguration, Types do not inherently
* have access to that TypeConfiguration (mainly because Type is an extension contract - meaning
* that Hibernate does not manage the full set of Types available in ever TypeConfiguration).
* However Types will often want access to the TypeConfiguration, which can be achieved by the
* Type simply implementing the {@link TypeConfigurationAware} interface.
*
* @author Steve Ebersole
*
* @since 5.3
*/
@Incubating
public class TypeConfiguration implements SessionFactoryObserver, Serializable {
private static final CoreMessageLogger log = messageLogger( Scope.class );
private final String uuid = LocalObjectUuidHelper.generateLocalObjectUuid();
private final Scope scope;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// things available during both boot and runtime lifecycle phases
private final transient JavaTypeDescriptorRegistry javaTypeDescriptorRegistry;
private final transient SqlTypeDescriptorRegistry sqlTypeDescriptorRegistry;
private final transient BasicTypeRegistry basicTypeRegistry;
private final transient Map> jdbcToHibernateTypeContributionMap = new HashMap<>();
public TypeConfiguration() {
this.scope = new Scope( this );
this.javaTypeDescriptorRegistry = new JavaTypeDescriptorRegistry( this );
this.sqlTypeDescriptorRegistry = new SqlTypeDescriptorRegistry( this );
this.basicTypeRegistry = new BasicTypeRegistry( this );
StandardBasicTypes.prime( this );
TypeConfigurationRegistry.INSTANCE.registerTypeConfiguration( this );
}
public String getUuid() {
return uuid;
}
public BasicTypeRegistry getBasicTypeRegistry() {
return basicTypeRegistry;
}
public JavaTypeDescriptorRegistry getJavaTypeDescriptorRegistry() {
return javaTypeDescriptorRegistry;
}
public SqlTypeDescriptorRegistry getSqlTypeDescriptorRegistry() {
return sqlTypeDescriptorRegistry;
}
public SqlTypeDescriptorIndicators getCurrentBaseSqlTypeIndicators() {
return scope.getCurrentBaseSqlTypeIndicators();
}
public Map> getJdbcToHibernateTypeContributionMap() {
return jdbcToHibernateTypeContributionMap;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Scoping
/**
* Obtain the MetadataBuildingContext currently scoping the
* TypeConfiguration.
*
* @apiNote This will throw an exception if the SessionFactory is not yet
* bound here. See {@link Scope} for more details regarding the stages
* a TypeConfiguration goes through
*
* @return
*/
public MetadataBuildingContext getMetadataBuildingContext() {
return scope.getMetadataBuildingContext();
}
public void scope(MetadataBuildingContext metadataBuildingContext) {
log.debugf( "Scoping TypeConfiguration [%s] to MetadataBuildingContext [%s]", this, metadataBuildingContext );
scope.setMetadataBuildingContext( metadataBuildingContext );
}
public DomainMetamodel scope(SessionFactoryImplementor sessionFactory) {
log.debugf( "Scoping TypeConfiguration [%s] to SessionFactoryImplementor [%s]", this, sessionFactory );
if ( scope.getMetadataBuildingContext() == null ) {
throw new IllegalStateException( "MetadataBuildingContext not known" );
}
scope.setSessionFactory( sessionFactory );
sessionFactory.addObserver( this );
return new DomainMetamodelImpl( sessionFactory, this );
}
/**
* Obtain the SessionFactory currently scoping the TypeConfiguration.
*
* @apiNote This will throw an exception if the SessionFactory is not yet
* bound here. See {@link Scope} for more details regarding the stages
* a TypeConfiguration goes through (this is "runtime stage")
*
* @return The SessionFactory
*
* @throws IllegalStateException if the TypeConfiguration is currently not
* associated with a SessionFactory (in "runtime stage").
*/
public SessionFactoryImplementor getSessionFactory() {
return scope.getSessionFactory();
}
/**
* Obtain the ServiceRegistry scoped to the TypeConfiguration.
*
* @apiNote Depending on what the {@link Scope} is currently scoped to will determine where the
* {@link ServiceRegistry} is obtained from.
*
* @return The ServiceRegistry
*/
public ServiceRegistry getServiceRegistry() {
return scope.getServiceRegistry();
}
@Override
public void sessionFactoryCreated(SessionFactory factory) {
// Instead of allowing scope#setSessionFactory to influence this, we use the SessionFactoryObserver callback
// to handle this, allowing any SessionFactory constructor code to be able to continue to have access to the
// MetadataBuildingContext through TypeConfiguration until this callback is fired.
log.tracef( "Handling #sessionFactoryCreated from [%s] for TypeConfiguration", factory );
scope.setMetadataBuildingContext( null );
}
@Override
public void sessionFactoryClosed(SessionFactory factory) {
log.tracef( "Handling #sessionFactoryClosed from [%s] for TypeConfiguration", factory );
TypeConfigurationRegistry.INSTANCE.deregisterTypeConfiguration( this );
scope.unsetSessionFactory( factory );
// todo (6.0) : finish this
// release Database, descriptor Maps, etc... things that are only
// valid while the TypeConfiguration is scoped to SessionFactory
}
public void addBasicTypeRegistrationContributions(List contributions) {
for ( BasicTypeRegistration basicTypeRegistration : contributions ) {
BasicType basicType = basicTypeRegistration.getBasicType();
basicTypeRegistry.register(
basicType,
basicTypeRegistration.getRegistrationKeys()
);
try {
int[] jdbcTypes = basicType.sqlTypes( null );
if ( jdbcTypes.length == 1 ) {
int jdbcType = jdbcTypes[0];
Set hibernateTypes = jdbcToHibernateTypeContributionMap.computeIfAbsent(
jdbcType,
k -> new HashSet<>()
);
hibernateTypes.add( basicType.getName() );
}
}
catch (Exception e) {
log.errorf( e, "Cannot register [%s] Hibernate Type contribution", basicType.getName() );
}
}
}
/**
* Encapsulation of lifecycle concerns for a TypeConfiguration, mainly in
* regards to eventually being associated with a SessionFactory. Goes
* 3 "lifecycle" stages, pertaining to {@link #getMetadataBuildingContext()}
* and {@link #getSessionFactory()}:
*
* * "Initialization" is where the {@link TypeConfiguration} is first
* built as the "boot model" ({@link org.hibernate.boot.model}) of
* the user's domain model is converted into the "runtime model"
* ({@link org.hibernate.metamodel.model}). During this phase,
* {@link #getMetadataBuildingContext()} will be accessible but
* {@link #getSessionFactory} will throw an exception.
* * "Runtime" is where the "runtime model" is accessible while the
* SessionFactory is still unclosed. During this phase
* {@link #getSessionFactory()} is accessible while
* {@link #getMetadataBuildingContext()} will now throw an
* exception
* * "Sunset" is after the SessionFactory has been closed. During this
* phase both {@link #getSessionFactory()} and
* {@link #getMetadataBuildingContext()} will now throw an exception
*
* Each stage or phase is consider a "scope" for the TypeConfiguration.
*/
private static class Scope implements Serializable {
private final TypeConfiguration typeConfiguration;
private transient MetadataBuildingContext metadataBuildingContext;
private transient SessionFactoryImplementor sessionFactory;
private String sessionFactoryName;
private String sessionFactoryUuid;
private transient SqlTypeDescriptorIndicators currentSqlTypeIndicators = new SqlTypeDescriptorIndicators() {
@Override
public TypeConfiguration getTypeConfiguration() {
return typeConfiguration;
}
@Override
public int getPreferredSqlTypeCodeForBoolean() {
return Types.BOOLEAN;
}
};
public Scope(TypeConfiguration typeConfiguration) {
this.typeConfiguration = typeConfiguration;
}
public SqlTypeDescriptorIndicators getCurrentBaseSqlTypeIndicators() {
return currentSqlTypeIndicators;
}
public MetadataBuildingContext getMetadataBuildingContext() {
if ( metadataBuildingContext == null ) {
throw new HibernateException( "TypeConfiguration is not currently scoped to MetadataBuildingContext" );
}
return metadataBuildingContext;
}
public ServiceRegistry getServiceRegistry() {
if ( metadataBuildingContext != null ) {
return metadataBuildingContext.getBootstrapContext().getServiceRegistry();
}
else if ( sessionFactory != null ) {
return sessionFactory.getServiceRegistry();
}
return null;
}
public void setMetadataBuildingContext(MetadataBuildingContext metadataBuildingContext) {
this.metadataBuildingContext = metadataBuildingContext;
}
public SessionFactoryImplementor getSessionFactory() {
if ( sessionFactory == null ) {
if ( sessionFactoryName == null && sessionFactoryUuid == null ) {
throw new HibernateException( "TypeConfiguration was not yet scoped to SessionFactory" );
}
sessionFactory = (SessionFactoryImplementor) SessionFactoryRegistry.INSTANCE.findSessionFactory(
sessionFactoryUuid,
sessionFactoryName
);
if ( sessionFactory == null ) {
throw new HibernateException(
"Could not find a SessionFactory [uuid=" + sessionFactoryUuid + ",name=" + sessionFactoryName + "]"
);
}
}
return sessionFactory;
}
/**
* Used by TypeFactory scoping.
*
* @param factory The SessionFactory that the TypeFactory is being bound to
*/
void setSessionFactory(SessionFactoryImplementor factory) {
if ( this.sessionFactory != null ) {
log.scopingTypesToSessionFactoryAfterAlreadyScoped( this.sessionFactory, factory );
}
else {
this.sessionFactoryUuid = factory.getUuid();
String sfName = factory.getSessionFactoryOptions().getSessionFactoryName();
if ( sfName == null ) {
final CfgXmlAccessService cfgXmlAccessService = factory.getServiceRegistry()
.getService( CfgXmlAccessService.class );
if ( cfgXmlAccessService.getAggregatedConfig() != null ) {
sfName = cfgXmlAccessService.getAggregatedConfig().getSessionFactoryName();
}
}
this.sessionFactoryName = sfName;
}
this.sessionFactory = factory;
}
public void unsetSessionFactory(SessionFactory factory) {
log.debugf( "Un-scoping TypeConfiguration [%s] from SessionFactory [%s]", this, factory );
this.sessionFactory = null;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Custom serialization hook
private Object readResolve() throws InvalidObjectException {
if ( sessionFactory == null ) {
if ( sessionFactoryName != null || sessionFactoryUuid != null ) {
sessionFactory = (SessionFactoryImplementor) SessionFactoryRegistry.INSTANCE.findSessionFactory(
sessionFactoryUuid,
sessionFactoryName
);
if ( sessionFactory == null ) {
throw new HibernateException(
"Could not find a SessionFactory [uuid=" + sessionFactoryUuid + ",name=" + sessionFactoryName + "]"
);
}
}
}
return this;
}
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Custom serialization hook
private Object readResolve() throws InvalidObjectException {
log.trace( "Resolving serialized TypeConfiguration - readResolve" );
return TypeConfigurationRegistry.INSTANCE.findTypeConfiguration( getUuid() );
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* @see QueryHelper#highestPrecedenceType2
*/
public SqmExpressable resolveArithmeticType(
SqmExpressable firstType,
SqmExpressable secondType,
BinaryArithmeticOperator operator) {
return resolveArithmeticType( firstType, secondType, operator == DIVIDE );
}
/**
* Determine the result type of an arithmetic operation as defined by the
* rules in section 6.5.7.1.
*
* @see QueryHelper#highestPrecedenceType2
*/
public SqmExpressable resolveArithmeticType(
SqmExpressable firstType,
SqmExpressable secondType,
boolean isDivision) {
if ( isDivision ) {
// covered under the note in 6.5.7.1 discussing the unportable
// "semantics of the SQL division operation"..
return getBasicTypeRegistry().getRegisteredType( Number.class.getName() );
}
// non-division
if ( matchesJavaType( firstType, Double.class ) ) {
return firstType;
}
else if ( matchesJavaType( secondType, Double.class ) ) {
return secondType;
}
else if ( matchesJavaType( firstType, Float.class ) ) {
return firstType;
}
else if ( matchesJavaType( secondType, Float.class ) ) {
return secondType;
}
else if ( matchesJavaType( firstType, BigDecimal.class ) ) {
return firstType;
}
else if ( matchesJavaType( secondType, BigDecimal.class ) ) {
return secondType;
}
else if ( matchesJavaType( firstType, BigInteger.class ) ) {
return firstType;
}
else if ( matchesJavaType( secondType, BigInteger.class ) ) {
return secondType;
}
else if ( matchesJavaType( firstType, Long.class ) ) {
return firstType;
}
else if ( matchesJavaType( secondType, Long.class ) ) {
return secondType;
}
else if ( matchesJavaType( firstType, Integer.class ) ) {
return firstType;
}
else if ( matchesJavaType( secondType, Integer.class ) ) {
return secondType;
}
else if ( matchesJavaType( firstType, Short.class ) ) {
return getBasicTypeRegistry().getRegisteredType( Integer.class.getName() );
}
else if ( matchesJavaType( secondType, Short.class ) ) {
return getBasicTypeRegistry().getRegisteredType( Integer.class.getName() );
}
else {
return getBasicTypeRegistry().getRegisteredType( Number.class.getName() );
}
}
@SuppressWarnings("unchecked")
private static boolean matchesJavaType(SqmExpressable type, Class javaType) {
assert javaType != null;
return type != null && javaType.isAssignableFrom( type.getExpressableJavaTypeDescriptor().getJavaType() );
}
private final ConcurrentHashMap basicTypeByJavaType = new ConcurrentHashMap<>();
public BasicType getBasicTypeForJavaType(Class javaType) {
final BasicType existing = basicTypeByJavaType.get( javaType );
if ( existing != null ) {
return existing;
}
final BasicType registeredType = getBasicTypeRegistry().getRegisteredType( javaType );
if ( registeredType != null ) {
basicTypeByJavaType.put( javaType, registeredType );
return registeredType;
}
return null;
}
public BasicType standardBasicTypeForJavaType(Class javaType) {
if ( javaType == null ) {
return null;
}
//noinspection unchecked
return standardBasicTypeForJavaType(
javaType,
javaTypeDescriptor -> new StandardBasicTypeImpl(
javaTypeDescriptor,
javaTypeDescriptor.getJdbcRecommendedSqlType( getCurrentBaseSqlTypeIndicators() )
)
);
}
public BasicType standardBasicTypeForJavaType(
Class javaType,
Function,BasicType> creator) {
if ( javaType == null ) {
return null;
}
return basicTypeByJavaType.computeIfAbsent(
javaType,
jt -> {
// See if one exists in the BasicTypeRegistry and use that one if so
final BasicType registeredType = basicTypeRegistry.getRegisteredType( javaType );
if ( registeredType != null ) {
return registeredType;
}
// otherwise, apply the creator
final JavaTypeDescriptor javaTypeDescriptor = javaTypeDescriptorRegistry.resolveDescriptor( javaType );
return creator.apply( javaTypeDescriptor );
}
);
}
}