Skip to content

Commit

Permalink
HHH-14885 - New composite user-type
Browse files Browse the repository at this point in the history
`@EmbeddableInstantiatorRegistration`
  • Loading branch information
sebersole committed Dec 2, 2021
1 parent 924c2b2 commit 61d178e
Show file tree
Hide file tree
Showing 13 changed files with 351 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,20 @@ include::{instantiatorTestDir}/embeddable/Person.java[tags=embeddable-instantiat
====


Additionally, instantiators can be registered:

[[embeddable-instantiator-registration-ex]]
.`@EmbeddableInstantiatorRegistration`
====
[source, JAVA, indent=0]
----
include::{instantiatorTestDir}/registered/Name.java[tags=embeddable-instantiator-registration]
include::{instantiatorTestDir}/registered/Person.java[tags=embeddable-instantiator-registration]
----
====




[[embeddable-multiple-namingstrategy]]
==== Embeddables and ImplicitNamingStrategy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Allows supplying a custom instantiator implementation
* Specifies a custom instantiator implementation
*/
@Target( {TYPE, FIELD, METHOD, ANNOTATION_TYPE} )
@Retention( RUNTIME )
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.annotations;

import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.hibernate.metamodel.spi.EmbeddableInstantiator;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Registers a custom instantiator implementation
*/
@Target( {TYPE, ANNOTATION_TYPE, PACKAGE} )
@Retention( RUNTIME )
@Repeatable( EmbeddableInstantiatorRegistrations.class )
public @interface EmbeddableInstantiatorRegistration {
Class<?> embeddableClass();
Class<? extends EmbeddableInstantiator> instantiator();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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.annotations;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.PACKAGE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* @author Steve Ebersole
*/
@Target( {TYPE, ANNOTATION_TYPE, PACKAGE} )
@Retention( RUNTIME )
public @interface EmbeddableInstantiatorRegistrations {
EmbeddableInstantiatorRegistration[] value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
import org.hibernate.mapping.SimpleValue;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UniqueKey;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.query.named.NamedObjectRepository;
import org.hibernate.query.sqm.function.SqmFunctionDescriptor;
import org.hibernate.type.descriptor.java.JavaType;
Expand Down Expand Up @@ -399,6 +400,26 @@ public void addJdbcTypeRegistration(int typeCode, JdbcType jdbcType) {
getTypeConfiguration().getJdbcTypeDescriptorRegistry().addDescriptor( typeCode, jdbcType );
}

private Map<Class<?>, Class<? extends EmbeddableInstantiator>> registeredInstantiators;

@Override
public void registerEmbeddableInstantiator(Class<?> embeddableType, Class<? extends EmbeddableInstantiator> instantiator) {
if ( registeredInstantiators == null ) {
registeredInstantiators = new HashMap<>();
}
registeredInstantiators.put( embeddableType, instantiator );
}

@Override
public Class<? extends EmbeddableInstantiator> findRegisteredEmbeddableInstantiator(Class<?> embeddableType) {
if ( registeredInstantiators == null ) {
return null;
}

return registeredInstantiators.get( embeddableType );
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// attribute converters

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.hibernate.mapping.MappedSuperclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Table;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;
import org.hibernate.type.descriptor.java.JavaType;
import org.hibernate.type.descriptor.jdbc.JdbcType;

Expand Down Expand Up @@ -319,6 +320,9 @@ void addTableNameBinding(
void addJavaTypeRegistration(Class<?> javaType, JavaType<?> jtd);
void addJdbcTypeRegistration(int typeCode, JdbcType jdbcType);

void registerEmbeddableInstantiator(Class<?> embeddableType, Class<? extends EmbeddableInstantiator> instantiator);
Class<? extends EmbeddableInstantiator> findRegisteredEmbeddableInstantiator(Class<?> embeddableType);

interface DelayedPropertyReferenceHandler extends Serializable {
void process(InFlightMetadataCollector metadataCollector);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DiscriminatorFormula;
import org.hibernate.annotations.DiscriminatorOptions;
import org.hibernate.annotations.EmbeddableInstantiatorRegistration;
import org.hibernate.annotations.EmbeddableInstantiatorRegistrations;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchProfile;
import org.hibernate.annotations.FetchProfiles;
Expand Down Expand Up @@ -350,6 +352,7 @@ public static void bindPackage(ClassLoaderService cls, String packageName, Metad
}

handleTypeDescriptorRegistrations( pckg, context );
bindEmbeddableInstantiatorRegistrations( pckg, context );

bindGenericGenerators( pckg, context );
bindQueries( pckg, context );
Expand Down Expand Up @@ -782,6 +785,7 @@ else if ( InheritanceType.SINGLE_TABLE.equals( inheritanceState.getType() ) ) {
// try to find class level generators
HashMap<String, IdentifierGeneratorDefinition> classGenerators = buildGenerators( clazzToProcess, context );
handleTypeDescriptorRegistrations( clazzToProcess, context );
bindEmbeddableInstantiatorRegistrations( clazzToProcess, context );

// check properties
final InheritanceState.ElementsToProcess elementsToProcess = inheritanceState.getElementsToProcess();
Expand Down Expand Up @@ -910,6 +914,36 @@ private static void handleJavaTypeDescriptorRegistration(
context.getMetadataCollector().addJavaTypeRegistration( annotation.javaType(), jtd );
}

private static void bindEmbeddableInstantiatorRegistrations(XAnnotatedElement annotatedElement, MetadataBuildingContext context) {
final ManagedBeanRegistry managedBeanRegistry = context.getBootstrapContext()
.getServiceRegistry()
.getService( ManagedBeanRegistry.class );

final EmbeddableInstantiatorRegistration instantiatorReg = annotatedElement.getAnnotation( EmbeddableInstantiatorRegistration.class );
if ( instantiatorReg != null ) {
handleEmbeddableInstantiatorRegistration( context, managedBeanRegistry, instantiatorReg );
}
else {
final EmbeddableInstantiatorRegistrations annotation = annotatedElement.getAnnotation( EmbeddableInstantiatorRegistrations.class );
if ( annotation != null ) {
final EmbeddableInstantiatorRegistration[] registrations = annotation.value();
for ( int i = 0; i < registrations.length; i++ ) {
handleEmbeddableInstantiatorRegistration( context, managedBeanRegistry, registrations[i] );
}
}
}
}

private static void handleEmbeddableInstantiatorRegistration(
MetadataBuildingContext context,
ManagedBeanRegistry managedBeanRegistry,
EmbeddableInstantiatorRegistration annotation) {
context.getMetadataCollector().registerEmbeddableInstantiator(
annotation.embeddableClass(),
annotation.instantiator()
);
}

/**
* Process all discriminator-related metadata per rules for "single table" inheritance
*/
Expand Down Expand Up @@ -2309,7 +2343,7 @@ else if ( !isId || !entityBinder.isIgnoreIdAnnotations() ) {
}

final AccessType propertyAccessor = entityBinder.getPropertyAccessor( property );
final Class<? extends EmbeddableInstantiator> customInstantiatorImpl = determineCustomInstantiator( property, returnedClass );
final Class<? extends EmbeddableInstantiator> customInstantiatorImpl = determineCustomInstantiator( property, returnedClass, context );

propertyBinder = bindComponent(
inferredData,
Expand Down Expand Up @@ -2457,7 +2491,10 @@ else if ( !isId || !entityBinder.isIgnoreIdAnnotations() ) {
}
}

private static Class<? extends EmbeddableInstantiator> determineCustomInstantiator(XProperty property, XClass returnedClass) {
private static Class<? extends EmbeddableInstantiator> determineCustomInstantiator(
XProperty property,
XClass returnedClass,
MetadataBuildingContext context) {
if ( property.isAnnotationPresent( EmbeddedId.class ) ) {
// we don't allow custom instantiators for composite ids
return null;
Expand All @@ -2473,6 +2510,11 @@ private static Class<? extends EmbeddableInstantiator> determineCustomInstantiat
return classAnnotation.value();
}

final Class embeddableClass = context.getBootstrapContext().getReflectionManager().toClass( returnedClass );
if ( embeddableClass != null ) {
return context.getMetadataCollector().findRegisteredEmbeddableInstantiator( embeddableClass );
}

return null;
}

Expand Down Expand Up @@ -2912,6 +2954,7 @@ public static Component fillComponent(
buildingContext.getMetadataCollector().addSecondPass( secondPass );

handleTypeDescriptorRegistrations( property, buildingContext );
bindEmbeddableInstantiatorRegistrations( property, buildingContext );
}
else {
Map<String, IdentifierGeneratorDefinition> localGenerators = new HashMap<>( buildGenerators( property, buildingContext ) );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1610,7 +1610,7 @@ else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().get
false,
false,
true,
resolveCustomInstantiator( property, elementClass ),
resolveCustomInstantiator( property, elementClass, buildingContext ),
buildingContext,
inheritanceStatePerClass
);
Expand Down Expand Up @@ -1672,17 +1672,25 @@ else if ( owner.getIdentifierMapper() != null && owner.getIdentifierMapper().get

}

private Class<? extends EmbeddableInstantiator> resolveCustomInstantiator(XProperty property, XClass embeddableClass) {
private Class<? extends EmbeddableInstantiator> resolveCustomInstantiator(
XProperty property,
XClass propertyClass,
MetadataBuildingContext context) {
final org.hibernate.annotations.EmbeddableInstantiator propertyAnnotation = property.getAnnotation( org.hibernate.annotations.EmbeddableInstantiator.class );
if ( propertyAnnotation != null ) {
return propertyAnnotation.value();
}

final org.hibernate.annotations.EmbeddableInstantiator classAnnotation = embeddableClass.getAnnotation( org.hibernate.annotations.EmbeddableInstantiator.class );
final org.hibernate.annotations.EmbeddableInstantiator classAnnotation = propertyClass.getAnnotation( org.hibernate.annotations.EmbeddableInstantiator.class );
if ( classAnnotation != null ) {
return classAnnotation.value();
}

final Class<?> embeddableClass = context.getBootstrapContext().getReflectionManager().toClass( propertyClass );
if ( embeddableClass != null ) {
return context.getMetadataCollector().findRegisteredEmbeddableInstantiator( embeddableClass );
}

return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.orm.test.mapping.embeddable.strategy.instantiator.registered;

import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* @author Steve Ebersole
*/
@DomainModel( annotatedClasses = { Person.class, Name.class } )
@SessionFactory
public class InstantiationTests {
@Test
public void basicTest(SessionFactoryScope scope) {
scope.inTransaction( (session) -> {
final Person mick = new Person( 1, new Name( "Mick", "Jagger" ) );
session.persist( mick );

final Person john = new Person( 2, new Name( "John", "Doe" ) );
john.addAlias( new Name( "Jon", "Doe" ) );
session.persist( john );
} );
scope.inTransaction( (session) -> {
final Person mick = session.createQuery( "from Person where id = 1", Person.class ).uniqueResult();
assertThat( mick.getName().getFirstName() ).isEqualTo( "Mick" );
} );
scope.inTransaction( (session) -> {
final Person john = session.createQuery( "from Person p join fetch p.aliases where p.id = 2", Person.class ).uniqueResult();
assertThat( john.getName().getFirstName() ).isEqualTo( "John" );
assertThat( john.getAliases() ).hasSize( 1 );
final Name alias = john.getAliases().iterator().next();
assertThat( alias.getFirstName() ).isEqualTo( "Jon" );
} );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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.orm.test.mapping.embeddable.strategy.instantiator.registered;

//tag::embeddable-instantiator-registration[]
public class Name {
private final String first;
private final String last;

private Name() {
throw new UnsupportedOperationException();
}

public Name(String first, String last) {
this.first = first;
this.last = last;
}

public String getFirstName() {
return first;
}

public String getLastName() {
return last;
}
}
//end::embeddable-instantiator-registration[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.orm.test.mapping.embeddable.strategy.instantiator.registered;

import java.util.function.Supplier;

import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.metamodel.spi.EmbeddableInstantiator;

/**
* @author Steve Ebersole
*/
public class NameInstantiator implements EmbeddableInstantiator {
@Override
public Object instantiate(Supplier<Object[]> valuesAccess, SessionFactoryImplementor sessionFactory) {
final Object[] values = valuesAccess.get();
// alphabetical
final String first = (String) values[0];
final String last = (String) values[1];
return new Name( first, last );
}

@Override
public boolean isInstance(Object object, SessionFactoryImplementor sessionFactory) {
return object instanceof Name;
}

@Override
public boolean isSameClass(Object object, SessionFactoryImplementor sessionFactory) {
return object.getClass().equals( Name.class );
}
}

0 comments on commit 61d178e

Please sign in to comment.