HHH-15847 introduce ConverterRegistry

This commit is contained in:
Gavin 2022-12-11 11:53:36 +01:00 committed by Gavin King
parent 4d2f4988c8
commit 2b7eb6fc1c
14 changed files with 154 additions and 75 deletions

View File

@ -8,6 +8,7 @@ package org.hibernate.boot;
import jakarta.persistence.AttributeConverter;
import org.hibernate.Remove;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.spi.MetadataBuildingContext;
@ -17,7 +18,10 @@ import org.hibernate.boot.spi.MetadataBuildingContext;
* the MetadataSources -> Metadata process.
*
* @author Steve Ebersole
*
* @deprecated no longer used
*/
@Deprecated(since = "6.2", forRemoval = true) @Remove
public interface AttributeConverterInfo {
Class<? extends AttributeConverter> getConverterClass();
ConverterDescriptor toConverterDescriptor(MetadataBuildingContext context);

View File

@ -40,6 +40,7 @@ import org.hibernate.boot.model.convert.internal.AttributeConverterManager;
import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterRegistry;
import org.hibernate.boot.model.convert.spi.RegisteredConversion;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.naming.ImplicitForeignKeyNameSource;
@ -125,7 +126,7 @@ import org.hibernate.usertype.UserType;
*
* @author Steve Ebersole
*/
public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector {
public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector, ConverterRegistry {
private static final CoreMessageLogger log = CoreLogging.messageLogger( InFlightMetadataCollectorImpl.class );
private final BootstrapContext bootstrapContext;
@ -493,6 +494,12 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// attribute converters
@Override
public ConverterRegistry getConverterRegistry() {
return this;
}
@Override
public void addAttributeConverter(Class<? extends AttributeConverter<?,?>> converterClass) {
attributeConverterManager.addConverter(
@ -500,6 +507,18 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
);
}
@Override
public void addOverridableConverter(Class<? extends AttributeConverter<?,?>> converterClass) {
attributeConverterManager.addConverter(
new ClassBasedConverterDescriptor( converterClass, getBootstrapContext().getClassmateContext() ) {
@Override
public boolean overrideable() {
return true;
}
}
);
}
@Override
public void addAttributeConverter(ConverterDescriptor descriptor) {
attributeConverterManager.addConverter( descriptor );
@ -515,6 +534,7 @@ public class InFlightMetadataCollectorImpl implements InFlightMetadataCollector
@Override
public ConverterAutoApplyHandler getAttributeConverterAutoApplyHandler() {
if ( !doneDialect ) {
// we want to delay this step until as late as possible
getDatabase().getDialect().registerAttributeConverters( this );
doneDialect = true;
}

View File

@ -16,7 +16,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import org.hibernate.AnnotationException;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.model.convert.spi.AutoApplicableConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler;
@ -30,6 +30,8 @@ import org.jboss.logging.Logger;
import com.fasterxml.classmate.ResolvedType;
import static java.util.stream.Collectors.toList;
import static org.hibernate.boot.model.convert.internal.ConverterHelper.resolveAttributeType;
import static org.hibernate.boot.model.convert.internal.ConverterHelper.resolveConverterClassParamTypes;
/**
@ -46,22 +48,18 @@ public class AttributeConverterManager implements ConverterAutoApplyHandler {
public void addConverter(ConverterDescriptor descriptor) {
if ( log.isTraceEnabled() ) {
log.tracef( "Starting AttributeConverterManager#addConverter : `%s`", descriptor.getAttributeConverterClass().getName() );
log.tracef( "Starting AttributeConverterManager#addConverter : `%s`",
descriptor.getAttributeConverterClass().getName() );
}
if ( registeredConversionsByDomainType != null ) {
final ResolvedType domainValueResolvedType = descriptor.getDomainValueResolvedType();
final Class<?> domainType = domainValueResolvedType.getErasedType();
final Class<?> domainType = descriptor.getDomainValueResolvedType().getErasedType();
final RegisteredConversion registeredConversion = registeredConversionsByDomainType.get( domainType );
if ( registeredConversion != null ) {
// we can skip the registering the converter...
// the RegisteredConversion will always take precedence
// we can skip registering the converter, the RegisteredConversion will always take precedence
if ( log.isDebugEnabled() ) {
log.debugf(
"Skipping registration of discovered AttributeConverter `%s` for auto-apply; there was" +
"already ",
descriptor.getAttributeConverterClass().getName()
);
log.debugf( "Skipping registration of discovered AttributeConverter `%s` for auto-apply",
descriptor.getAttributeConverterClass().getName() );
}
return;
}
@ -77,7 +75,7 @@ public class AttributeConverterManager implements ConverterAutoApplyHandler {
);
if ( old != null ) {
throw new AssertionFailure(
throw new HibernateException(
String.format(
Locale.ENGLISH,
"AttributeConverter class [%s] registered multiple times",
@ -95,7 +93,8 @@ public class AttributeConverterManager implements ConverterAutoApplyHandler {
final Class<?> domainType;
if ( conversion.getExplicitDomainType().equals( void.class ) ) {
// the registration did not define an explicit domain-type, so inspect the converter
final List<ResolvedType> converterParamTypes = resolveConverterClassParamTypes( conversion.getConverterType(), context.getClassmateContext() );
final List<ResolvedType> converterParamTypes =
resolveConverterClassParamTypes( conversion.getConverterType(), context.getClassmateContext() );
domainType = converterParamTypes.get( 0 ).getErasedType();
}
else {
@ -106,14 +105,13 @@ public class AttributeConverterManager implements ConverterAutoApplyHandler {
final RegisteredConversion existingRegistration = registeredConversionsByDomainType.get( domainType );
if ( existingRegistration != null ) {
if ( !conversion.equals( existingRegistration ) ) {
throw new AnnotationException(
"Conflicting '@ConverterRegistration' descriptors for attribute converter '"
+ conversion.getConverterType().getName() + "'"
);
throw new AnnotationException( "Conflicting '@ConverterRegistration' descriptors for attribute converter '"
+ conversion.getConverterType().getName() + "'" );
}
else {
if ( log.isDebugEnabled() ) {
log.debugf( "Skipping duplicate '@ConverterRegistration' for '%s'", conversion.getConverterType().getName() );
log.debugf( "Skipping duplicate '@ConverterRegistration' for '%s'",
conversion.getConverterType().getName() );
}
}
}
@ -123,10 +121,8 @@ public class AttributeConverterManager implements ConverterAutoApplyHandler {
if ( attributeConverterDescriptorsByClass != null ) {
final ConverterDescriptor removed = attributeConverterDescriptorsByClass.remove( conversion.getConverterType() );
if ( removed != null && log.isDebugEnabled() ) {
log.debugf(
"Removed potentially auto-applicable converter `%s` due to @ConverterRegistration",
removed.getAttributeConverterClass().getName()
);
log.debugf( "Removed potentially auto-applicable converter `%s` due to @ConverterRegistration",
removed.getAttributeConverterClass().getName() );
}
}
@ -155,6 +151,7 @@ public class AttributeConverterManager implements ConverterAutoApplyHandler {
return siteDescriptor;
}
}
@Override
public ConverterDescriptor findAutoApplyConverterForAttribute(
XProperty property,
@ -176,8 +173,9 @@ public class AttributeConverterManager implements ConverterAutoApplyHandler {
MetadataBuildingContext context) {
if ( registeredConversionsByDomainType != null ) {
// we had registered conversions - see if any of them match and, if so, use that conversion
final ResolvedType resolveAttributeType = ConverterHelper.resolveAttributeType( xProperty, context );
final RegisteredConversion registrationForDomainType = registeredConversionsByDomainType.get( resolveAttributeType.getErasedType() );
final ResolvedType resolveAttributeType = resolveAttributeType( xProperty, context );
final RegisteredConversion registrationForDomainType =
registeredConversionsByDomainType.get( resolveAttributeType.getErasedType() );
if ( registrationForDomainType != null ) {
return registrationForDomainType.isAutoApply() ? registrationForDomainType.getConverterDescriptor() : null;
}
@ -210,11 +208,16 @@ public class AttributeConverterManager implements ConverterAutoApplyHandler {
}
if ( matches.size() == 1 ) {
return matches.get( 0 );
return matches.get(0);
}
List<ConverterDescriptor> filtered = matches.stream().filter( match -> !match.overrideable() ).collect( toList() );
if ( filtered.size() == 1 ) {
return filtered.get(0);
}
// otherwise, we had multiple matches
throw new RuntimeException(
throw new HibernateException(
String.format(
Locale.ROOT,
"Multiple auto-apply converters matched %s [%s.%s] : %s",

View File

@ -45,4 +45,11 @@ public interface ConverterDescriptor {
* Factory for the runtime representation of the converter
*/
JpaAttributeConverter<?,?> createJpaAttributeConverter(JpaAttributeConverterCreationContext context);
/**
* Can this converter be overridden by other competing converters?
*/
default boolean overrideable() {
return false;
}
}

View File

@ -0,0 +1,35 @@
/*
* 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.boot.model.convert.spi;
import jakarta.persistence.AttributeConverter;
import org.hibernate.Incubating;
/**
* @author Gavin King
*/
@Incubating
public interface ConverterRegistry {
/**
* Apply the descriptor for an {@link AttributeConverter}
*/
void addAttributeConverter(ConverterDescriptor descriptor);
/**
* Apply an {@link AttributeConverter}
*/
void addAttributeConverter(Class<? extends AttributeConverter<?,?>> converterClass);
/**
* Apply an {@link AttributeConverter} that may be overridden by competing converters
*/
void addOverridableConverter(Class<? extends AttributeConverter<?,?>> converterClass);
void addRegisteredConversion(RegisteredConversion conversion);
ConverterAutoApplyHandler getAttributeConverterAutoApplyHandler();
}

View File

@ -16,11 +16,10 @@ import org.hibernate.annotations.Imported;
import org.hibernate.annotations.common.reflection.MetadataProviderInjector;
import org.hibernate.annotations.common.reflection.ReflectionManager;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.boot.AttributeConverterInfo;
import org.hibernate.boot.internal.MetadataBuildingContextRootImpl;
import org.hibernate.boot.jaxb.mapping.JaxbEntityMappings;
import org.hibernate.boot.jaxb.spi.Binding;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterRegistry;
import org.hibernate.boot.model.process.spi.ManagedResources;
import org.hibernate.boot.model.source.spi.MetadataSourceProcessor;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
@ -29,7 +28,6 @@ import org.hibernate.boot.spi.JpaOrmXmlPersistenceUnitDefaultAware.JpaOrmXmlPers
import org.hibernate.boot.spi.MetadataBuildingOptions;
import org.hibernate.cfg.AnnotationBinder;
import org.hibernate.cfg.InheritanceState;
import org.hibernate.cfg.annotations.reflection.AttributeConverterDefinitionCollector;
import org.hibernate.cfg.annotations.reflection.internal.JPAXMLOverriddenMetadataProvider;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
@ -74,7 +72,7 @@ public class AnnotationMetadataSourceProcessorImpl implements MetadataSourceProc
annotatedPackages.addAll( managedResources.getAnnotatedPackageNames() );
}
final AttributeConverterManager attributeConverterManager = new AttributeConverterManager( rootMetadataBuildingContext );
final ConverterRegistry converterRegistry = rootMetadataBuildingContext.getMetadataCollector().getConverterRegistry();
this.classLoaderService = rootMetadataBuildingContext.getBuildingOptions().getServiceRegistry().getService( ClassLoaderService.class );
MetadataBuildingOptions metadataBuildingOptions = rootMetadataBuildingContext.getBuildingOptions();
@ -95,26 +93,26 @@ public class AnnotationMetadataSourceProcessorImpl implements MetadataSourceProc
xClasses.add( toXClass( className, reflectionManager, classLoaderService ) );
}
}
jpaMetadataProvider.getXMLContext().applyDiscoveredAttributeConverters( attributeConverterManager );
jpaMetadataProvider.getXMLContext().applyDiscoveredAttributeConverters( converterRegistry );
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
for ( String className : managedResources.getAnnotatedClassNames() ) {
final Class<?> annotatedClass = classLoaderService.classForName( className );
categorizeAnnotatedClass( annotatedClass, attributeConverterManager );
categorizeAnnotatedClass( annotatedClass, converterRegistry );
}
for ( Class<?> annotatedClass : managedResources.getAnnotatedClassReferences() ) {
categorizeAnnotatedClass( annotatedClass, attributeConverterManager );
categorizeAnnotatedClass( annotatedClass, converterRegistry );
}
}
private void categorizeAnnotatedClass(Class<?> annotatedClass, AttributeConverterManager attributeConverterManager) {
private void categorizeAnnotatedClass(Class<?> annotatedClass, ConverterRegistry converterRegistry) {
final XClass xClass = reflectionManager.toXClass( annotatedClass );
// categorize it, based on assumption it does not fall into multiple categories
if ( xClass.isAnnotationPresent( Converter.class ) ) {
//noinspection unchecked
attributeConverterManager.addAttributeConverter( (Class<? extends AttributeConverter<?,?>>) annotatedClass );
converterRegistry.addAttributeConverter( (Class<? extends AttributeConverter<?,?>>) annotatedClass );
}
else if ( xClass.isAnnotationPresent( Entity.class )
|| xClass.isAnnotationPresent( MappedSuperclass.class )
@ -287,28 +285,4 @@ public class AnnotationMetadataSourceProcessorImpl implements MetadataSourceProc
@Override
public void finishUp() {
}
private static class AttributeConverterManager implements AttributeConverterDefinitionCollector {
private final MetadataBuildingContextRootImpl rootMetadataBuildingContext;
public AttributeConverterManager(MetadataBuildingContextRootImpl rootMetadataBuildingContext) {
this.rootMetadataBuildingContext = rootMetadataBuildingContext;
}
@Override
public void addAttributeConverter(AttributeConverterInfo info) {
rootMetadataBuildingContext.getMetadataCollector().addAttributeConverter(
info.toConverterDescriptor( rootMetadataBuildingContext )
);
}
@Override
public void addAttributeConverter(ConverterDescriptor descriptor) {
rootMetadataBuildingContext.getMetadataCollector().addAttributeConverter( descriptor );
}
public void addAttributeConverter(Class<? extends AttributeConverter<?,?>> converterClass) {
rootMetadataBuildingContext.getMetadataCollector().addAttributeConverter( converterClass );
}
}
}

View File

@ -10,7 +10,6 @@ import java.io.Serializable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.function.Function;
import org.hibernate.DuplicateMappingException;
@ -24,6 +23,7 @@ import org.hibernate.boot.model.TypeDefinition;
import org.hibernate.boot.model.TypeDefinitionRegistry;
import org.hibernate.boot.model.convert.spi.ConverterAutoApplyHandler;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterRegistry;
import org.hibernate.boot.model.convert.spi.RegisteredConversion;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
@ -225,17 +225,37 @@ public interface InFlightMetadataCollector extends Mapping, MetadataImplementor
void addIdentifierGenerator(IdentifierGeneratorDefinition generatorDefinition);
/**
* Apply the descriptor for an {@link AttributeConverter}
* Obtain the {@link ConverterRegistry} which may be
* used to register {@link AttributeConverter}s.
*/
ConverterRegistry getConverterRegistry();
/**
* Apply the descriptor for an {@link AttributeConverter}
*
* @deprecated use {@link #getConverterRegistry()}
*/
@Deprecated(since = "6.2")
void addAttributeConverter(ConverterDescriptor descriptor);
/**
* Apply an {@link AttributeConverter}
*
* @deprecated use {@link #getConverterRegistry()}
*/
@Deprecated(since = "6.2")
void addAttributeConverter(Class<? extends AttributeConverter<?,?>> converterClass);
/**
* @deprecated use {@link #getConverterRegistry()}
*/
@Deprecated(since = "6.2")
void addRegisteredConversion(RegisteredConversion conversion);
/**
* @deprecated use {@link #getConverterRegistry()}
*/
@Deprecated(since = "6.2")
ConverterAutoApplyHandler getAttributeConverterAutoApplyHandler();

View File

@ -104,6 +104,7 @@ public abstract class AbstractPropertyHolder implements PropertyHolder {
log.debugf( "Attempting to locate auto-apply AttributeConverter for property [%s:%s]", path, property.getName() );
return context.getMetadataCollector()
.getConverterRegistry()
.getAttributeConverterAutoApplyHandler()
.findAutoApplyConverterForAttribute( property, context );
}

View File

@ -745,7 +745,7 @@ public final class AnnotationBinder {
private static void handleConverterRegistration(ConverterRegistration registration, MetadataBuildingContext context) {
final InFlightMetadataCollector metadataCollector = context.getMetadataCollector();
metadataCollector.addRegisteredConversion(
metadataCollector.getConverterRegistry().addRegisteredConversion(
new RegisteredConversion(
registration.domainType(),
registration.converter(),

View File

@ -20,7 +20,6 @@ import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.Collection;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.KeyValue;
@ -408,6 +407,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
// todo : do we need to pass along `XClass elementXClass`?
return getContext().getMetadataCollector()
.getConverterRegistry()
.getAttributeConverterAutoApplyHandler()
.findAutoApplyConverterForCollectionElement( collectionXProperty, getContext() );
}
@ -436,6 +436,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
// todo : do we need to pass along `XClass keyXClass`?
return getContext().getMetadataCollector()
.getConverterRegistry()
.getAttributeConverterAutoApplyHandler()
.findAutoApplyConverterForMapKey( mapXProperty, getContext() );
}

View File

@ -6,13 +6,16 @@
*/
package org.hibernate.cfg.annotations.reflection;
import org.hibernate.Remove;
import org.hibernate.boot.AttributeConverterInfo;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterRegistry;
/**
* @author Steve Ebersole
*
* @deprecated no longer used, use {@link ConverterRegistry}
*/
public interface AttributeConverterDefinitionCollector {
@Deprecated(since = "6.2", forRemoval = true) @Remove
public interface AttributeConverterDefinitionCollector extends ConverterRegistry {
void addAttributeConverter(AttributeConverterInfo info);
void addAttributeConverter(ConverterDescriptor descriptor);
}

View File

@ -25,10 +25,10 @@ import org.hibernate.boot.jaxb.mapping.JaxbPersistenceUnitMetadata;
import org.hibernate.boot.jaxb.mapping.ManagedType;
import org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterDescriptor;
import org.hibernate.boot.model.convert.spi.ConverterRegistry;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.boot.spi.BootstrapContext;
import org.hibernate.boot.spi.ClassLoaderAccess;
import org.hibernate.cfg.annotations.reflection.AttributeConverterDefinitionCollector;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;
@ -232,9 +232,9 @@ public class XMLContext implements Serializable {
private final List<ConverterDescriptor> converterDescriptors = new ArrayList<>();
public void applyDiscoveredAttributeConverters(AttributeConverterDefinitionCollector collector) {
public void applyDiscoveredAttributeConverters(ConverterRegistry converterRegistry) {
for ( ConverterDescriptor descriptor : converterDescriptors ) {
collector.addAttributeConverter( descriptor );
converterRegistry.addAttributeConverter( descriptor );
}
converterDescriptors.clear();
}

View File

@ -45,9 +45,9 @@ import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.ScrollMode;
import org.hibernate.boot.TempTableDdlTransactionHandling;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.convert.spi.ConverterRegistry;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Sequence;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.function.CastFunction;
@ -348,7 +348,13 @@ public abstract class Dialect implements ConversionContext {
Boolean.toString( getDefaultUseGetGeneratedKeys() ) );
}
public void registerAttributeConverters(InFlightMetadataCollector metadataCollector) {}
/**
* Register any {@link jakarta.persistence.AttributeConverter}s needed.
* <p>
* Good citizens should use {@link ConverterRegistry#addOverridableConverter}
* so as not to interfere with user-registered converters.
*/
public void registerAttributeConverters(ConverterRegistry converterRegistry) {}
/**
* Register ANSI-standard column types using the length limits defined

View File

@ -20,7 +20,7 @@ import jakarta.persistence.Converter;
import org.hibernate.LockOptions;
import org.hibernate.QueryTimeoutException;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.model.convert.spi.ConverterRegistry;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.function.CommonFunctionFactory;
import org.hibernate.dialect.function.ModeStatsModeEmulation;
@ -642,6 +642,10 @@ public class OracleDialect extends Dialect {
}
}
/**
* A dummy converter responsible for creating the check constraints
* for Java {@code boolean} mapped to {@code number(1,0)}.
*/
@Converter(autoApply = true)
static class BooleanBooleanConverter implements AttributeConverter<Boolean,Boolean>, BasicValueConverter<Boolean,Boolean> {
@Override
@ -680,8 +684,9 @@ public class OracleDialect extends Dialect {
}
}
public void registerAttributeConverters(InFlightMetadataCollector metadataCollector) {
metadataCollector.addAttributeConverter( BooleanBooleanConverter.class );
@Override
public void registerAttributeConverters(ConverterRegistry converterRegistry) {
converterRegistry.addOverridableConverter( BooleanBooleanConverter.class );
}
@Override