HHH-16006 - Implement an "additional mapping" contributor SPI

This commit is contained in:
Steve Ebersole 2023-01-19 16:30:32 -06:00
parent 863faf4c98
commit a552a73632
9 changed files with 388 additions and 63 deletions

View File

@ -6,22 +6,28 @@
*/
package org.hibernate.boot.model.process.spi;
import java.io.InputStream;
import java.sql.Types;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jakarta.persistence.AttributeConverter;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.internal.InFlightMetadataCollectorImpl;
import org.hibernate.boot.internal.MetadataBuildingContextRootImpl;
import org.hibernate.boot.jaxb.Origin;
import org.hibernate.boot.jaxb.SourceType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmHibernateMapping;
import org.hibernate.boot.jaxb.internal.MappingBinder;
import org.hibernate.boot.jaxb.mapping.JaxbEntityMappings;
import org.hibernate.boot.jaxb.spi.BindableMappingDescriptor;
import org.hibernate.boot.jaxb.spi.Binding;
import org.hibernate.boot.model.TypeContributions;
import org.hibernate.boot.model.TypeContributor;
import org.hibernate.boot.model.convert.spi.ConverterRegistry;
@ -52,7 +58,6 @@ import org.hibernate.dialect.Dialect;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Table;
import org.hibernate.type.BasicType;
import org.hibernate.type.BasicTypeRegistry;
@ -71,6 +76,8 @@ import org.hibernate.type.spi.TypeConfiguration;
import org.jboss.jandex.IndexView;
import org.jboss.logging.Logger;
import jakarta.persistence.AttributeConverter;
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForArray;
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForDuration;
import static org.hibernate.internal.util.config.ConfigurationHelper.getPreferredSqlTypeCodeForInstant;
@ -333,14 +340,6 @@ public class MetadataBuildingProcess {
MetadataBuildingOptions options,
ClassLoaderService classLoaderService,
MetadataBuildingContextRootImpl rootMetadataBuildingContext) {
final EntityHierarchyBuilder hierarchyBuilder = new EntityHierarchyBuilder();
final AdditionalMappingContributionsImpl contributions = new AdditionalMappingContributionsImpl(
metadataCollector,
options,
rootMetadataBuildingContext,
hierarchyBuilder
);
final MappingBinder mappingBinder;
if ( options.isXmlMappingEnabled() ) {
mappingBinder = new MappingBinder(
@ -362,6 +361,13 @@ public class MetadataBuildingProcess {
mappingBinder = null;
}
final AdditionalMappingContributionsImpl contributions = new AdditionalMappingContributionsImpl(
metadataCollector,
options,
mappingBinder,
rootMetadataBuildingContext
);
final Collection<AdditionalMappingContributor> additionalMappingContributors = classLoaderService.loadJavaServices( AdditionalMappingContributor.class );
additionalMappingContributors.forEach( (contributor) -> {
contributions.setCurrentContributor( contributor.getContributorName() );
@ -369,7 +375,7 @@ public class MetadataBuildingProcess {
contributor.contribute(
contributions,
metadataCollector,
mappingBinder,
classLoaderService,
rootMetadataBuildingContext
);
}
@ -378,28 +384,31 @@ public class MetadataBuildingProcess {
}
} );
final ModelBinder binder = ModelBinder.prepare( rootMetadataBuildingContext );
for ( EntityHierarchySourceImpl entityHierarchySource : hierarchyBuilder.buildHierarchies() ) {
binder.bindEntityHierarchy( entityHierarchySource );
}
contributions.complete();
}
private static class AdditionalMappingContributionsImpl implements AdditionalMappingContributions {
private final InFlightMetadataCollectorImpl metadataCollector;
private final MetadataBuildingOptions options;
private final MappingBinder mappingBinder;
private final MetadataBuildingContextRootImpl rootMetadataBuildingContext;
private final EntityHierarchyBuilder hierarchyBuilder;
private final EntityHierarchyBuilder hierarchyBuilder = new EntityHierarchyBuilder();
private List<Class<?>> additionalEntityClasses;
private List<JaxbEntityMappings> additionalJaxbMappings;
private boolean extraHbmXml = false;
private String currentContributor;
public AdditionalMappingContributionsImpl(
InFlightMetadataCollectorImpl metadataCollector,
MetadataBuildingOptions options,
MetadataBuildingContextRootImpl rootMetadataBuildingContext,
EntityHierarchyBuilder hierarchyBuilder) {
MappingBinder mappingBinder,
MetadataBuildingContextRootImpl rootMetadataBuildingContext) {
this.metadataCollector = metadataCollector;
this.options = options;
this.mappingBinder = mappingBinder;
this.rootMetadataBuildingContext = rootMetadataBuildingContext;
this.hierarchyBuilder = hierarchyBuilder;
}
public void setCurrentContributor(String contributor) {
@ -407,22 +416,53 @@ public class MetadataBuildingProcess {
}
@Override
public void contributeBinding(JaxbHbmHibernateMapping hbmJaxbBinding, Origin origin) {
public void contributeEntity(Class<?> entityType) {
if ( additionalEntityClasses == null ) {
additionalEntityClasses = new ArrayList<>();
}
additionalEntityClasses.add( entityType );
}
@Override
public void contributeBinding(InputStream xmlStream) {
final Origin origin = new Origin( SourceType.INPUT_STREAM, null );
final Binding<BindableMappingDescriptor> binding = mappingBinder.bind( xmlStream, origin );
final BindableMappingDescriptor bindingRoot = binding.getRoot();
if ( bindingRoot instanceof JaxbHbmHibernateMapping ) {
contributeBinding( (JaxbHbmHibernateMapping) bindingRoot );
}
else {
contributeBinding( (JaxbEntityMappings) bindingRoot );
}
}
@Override
public void contributeBinding(JaxbEntityMappings mappingJaxbBinding) {
if ( ! options.isXmlMappingEnabled() ) {
return;
}
hierarchyBuilder.indexMappingDocument( new MappingDocument(
currentContributor,
hbmJaxbBinding,
origin,
rootMetadataBuildingContext
) );
if ( additionalJaxbMappings == null ) {
additionalJaxbMappings = new ArrayList<>();
}
additionalJaxbMappings.add( mappingJaxbBinding );
}
@Override
public void contributeEntity(PersistentClass entity) {
metadataCollector.addEntityBinding( entity );
public void contributeBinding(JaxbHbmHibernateMapping hbmJaxbBinding) {
if ( ! options.isXmlMappingEnabled() ) {
return;
}
extraHbmXml = true;
hierarchyBuilder.indexMappingDocument( new MappingDocument(
currentContributor,
hbmJaxbBinding,
new Origin( SourceType.OTHER, null ),
rootMetadataBuildingContext
) );
}
@Override
@ -448,6 +488,25 @@ public class MetadataBuildingProcess {
public void contributeAuxiliaryDatabaseObject(AuxiliaryDatabaseObject auxiliaryDatabaseObject) {
metadataCollector.addAuxiliaryDatabaseObject( auxiliaryDatabaseObject );
}
public void complete() {
// annotations / orm.xml
if ( additionalEntityClasses != null || additionalJaxbMappings != null ) {
AnnotationMetadataSourceProcessorImpl.processAdditionalMappings(
additionalEntityClasses,
additionalJaxbMappings,
rootMetadataBuildingContext
);
}
// hbm.xml
if ( extraHbmXml ) {
final ModelBinder binder = ModelBinder.prepare( rootMetadataBuildingContext );
for ( EntityHierarchySourceImpl entityHierarchySource : hierarchyBuilder.buildHierarchies() ) {
binder.bindEntityHierarchy( entityHierarchySource );
}
}
}
}
private static void processAdditionalJaxbMappingProducer(

View File

@ -9,7 +9,9 @@ package org.hibernate.boot.model.relational;
import org.hibernate.mapping.Contributable;
/**
* Contributable specialization for Tables and Sequences
* Database objects (table, sequence, etc) which are associated with
* a {@linkplain #getContributor() contributor} (ORM, Envers, etc) and
* can be selectively exported per contributor
*/
public interface ContributableDatabaseObject extends Contributable, Exportable {
}

View File

@ -56,6 +56,9 @@ public class AnnotationMetadataSourceProcessorImpl implements MetadataSourceProc
private final List<XClass> xClasses = new ArrayList<>();
private final ClassLoaderService classLoaderService;
/**
* Normal constructor used while processing {@linkplain org.hibernate.boot.MetadataSources mapping sources}
*/
public AnnotationMetadataSourceProcessorImpl(
ManagedResources managedResources,
final MetadataBuildingContextRootImpl rootMetadataBuildingContext,
@ -104,6 +107,54 @@ public class AnnotationMetadataSourceProcessorImpl implements MetadataSourceProc
}
}
/**
* Used as part of processing
* {@linkplain org.hibernate.boot.spi.AdditionalMappingContributions#contributeEntity(Class)} "additional" mappings}
*/
public static void processAdditionalMappings(
List<Class<?>> additionalClasses,
List<JaxbEntityMappings> additionalJaxbMappings,
MetadataBuildingContextRootImpl rootMetadataBuildingContext) {
final AnnotationMetadataSourceProcessorImpl processor = new AnnotationMetadataSourceProcessorImpl( rootMetadataBuildingContext );
if ( additionalJaxbMappings != null && rootMetadataBuildingContext.getBuildingOptions().isXmlMappingEnabled() ) {
final ConverterRegistry converterRegistry = rootMetadataBuildingContext.getMetadataCollector().getConverterRegistry();
final MetadataProviderInjector injector = (MetadataProviderInjector) processor.reflectionManager;
final JPAXMLOverriddenMetadataProvider metadataProvider = (JPAXMLOverriddenMetadataProvider) injector.getMetadataProvider();
for ( int i = 0; i < additionalJaxbMappings.size(); i++ ) {
final List<String> classNames = metadataProvider.getXMLContext().addDocument( additionalJaxbMappings.get( i ) );
for ( String className : classNames ) {
final XClass xClass = processor.toXClass( className, processor.reflectionManager, processor.classLoaderService );
processor.xClasses.add( xClass );
}
}
metadataProvider.getXMLContext().applyDiscoveredAttributeConverters( converterRegistry );
}
for ( int i = 0; i < additionalClasses.size(); i++ ) {
final XClass xClass = processor.reflectionManager.toXClass( additionalClasses.get( i ) );
if ( !xClass.isAnnotationPresent( Entity.class ) ) {
log.debugf( "@Entity not found on additional entity class - `%s`" );
continue;
}
processor.xClasses.add( xClass );
}
processor.processEntityHierarchies( new LinkedHashSet<>() );
}
/**
* Form used from {@link #processAdditionalMappings}
*/
private AnnotationMetadataSourceProcessorImpl(MetadataBuildingContextRootImpl rootMetadataBuildingContext) {
this.rootMetadataBuildingContext = rootMetadataBuildingContext;
this.jandexView = null;
this.reflectionManager = rootMetadataBuildingContext.getBootstrapContext().getReflectionManager();
this.classLoaderService = rootMetadataBuildingContext.getBuildingOptions().getServiceRegistry().getService( ClassLoaderService.class );
}
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

View File

@ -6,30 +6,47 @@
*/
package org.hibernate.boot.spi;
import java.io.InputStream;
import org.hibernate.Incubating;
import org.hibernate.boot.jaxb.Origin;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmHibernateMapping;
import org.hibernate.boot.jaxb.mapping.JaxbEntityMappings;
import org.hibernate.boot.model.relational.AuxiliaryDatabaseObject;
import org.hibernate.boot.model.relational.Sequence;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Table;
/**
* Collector for contributions from {@linkplain AdditionalMappingContributor contributors}
*
* @author Steve Ebersole
*
* @since 6.2
*/
@Incubating
public interface AdditionalMappingContributions {
/**
* Contribute mappings in the form of {@code hbm.xml} JAXB bindings
* Contribute a presumably annotated entity class.
*/
void contributeBinding(JaxbHbmHibernateMapping hbmJaxbBinding, Origin origin);
void contributeEntity(Class<?> entityType);
/**
* Contribute a materialized PersistentClass
* Contribute mappings from the InputStream containing an XML mapping document.
*/
void contributeEntity(PersistentClass entity);
void contributeBinding(InputStream xmlStream);
/**
* Contribute mappings in the form of {@code hbm.xml} JAXB bindings.
*
* @deprecated {@code hbm.xml} mapping file support is deprecated. Use
* {@linkplain #contributeBinding(JaxbEntityMappings) extended orm.xml}
* bindings instead.
*/
void contributeBinding(JaxbHbmHibernateMapping hbmJaxbBinding);
/**
* Contribute mappings in the form of (extended) {@code orm.xml} JAXB bindings
*/
void contributeBinding(JaxbEntityMappings mappingJaxbBinding);
/**
* Contribute a materialized Table

View File

@ -7,11 +7,10 @@
package org.hibernate.boot.spi;
import org.hibernate.Incubating;
import org.hibernate.boot.jaxb.internal.MappingBinder;
import org.hibernate.boot.ResourceStreamLocator;
/**
* Contract allowing pluggable contributions of additional
* mapping objects.
* Contract allowing pluggable contributions of additional mapping objects.
*
* Resolvable as a {@linkplain java.util.ServiceLoader Java service}.
*
@ -20,7 +19,9 @@ import org.hibernate.boot.jaxb.internal.MappingBinder;
@Incubating
public interface AdditionalMappingContributor {
/**
* The name of this contributor.
* The name of this contributor. May be {@code null}.
*
* @see org.hibernate.mapping.Contributable
*/
default String getContributorName() {
return null;
@ -29,15 +30,14 @@ public interface AdditionalMappingContributor {
/**
* Contribute the additional mappings
*
* @param contributions Collector of the contributions
* @param metadata Current (live) metadata
* @param jaxbBinder JAXB binding support for XML documents. May be {@code null}
* if XML processing is {@linkplain MetadataBuildingOptions#isXmlMappingEnabled() disabled}
* @param buildingContext Access to useful contextual details
* @param contributions Collector of the contributions.
* @param metadata Current (live) metadata. Can be used to access already known mappings.
* @param resourceStreamLocator Delegate for locating XML resources via class-path lookup.
* @param buildingContext Access to useful contextual references.
*/
void contribute(
AdditionalMappingContributions contributions,
InFlightMetadataCollector metadata,
MappingBinder jaxbBinder,
ResourceStreamLocator resourceStreamLocator,
MetadataBuildingContext buildingContext);
}

View File

@ -6,12 +6,20 @@
*/
package org.hibernate.mapping;
import org.hibernate.boot.model.relational.ContributableDatabaseObject;
/**
* Part of the mapping model that is associated with a contributor.
* ORM, Envers, Search, etc
* Parts of the mapping model which are associated with a
* {@linkplain #getContributor() contributor} (ORM, Envers, etc).
* <p/>
* The most useful aspect of this is the {@link ContributableDatabaseObject}
* specialization.
*
* @author Steve Ebersole
*/
public interface Contributable {
/**
* The name of the contributor which contributed this
*/
String getContributor();
}

View File

@ -0,0 +1,185 @@
/*
* 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.intg;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.hibernate.boot.ResourceStreamLocator;
import org.hibernate.boot.spi.AdditionalMappingContributions;
import org.hibernate.boot.spi.AdditionalMappingContributor;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry;
import org.hibernate.testing.orm.junit.BootstrapServiceRegistry.JavaService;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.DomainModelScope;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Steve Ebersole
*
* @implNote hibernate-envers is already a full testing of contributing a {@code hbm.xml}
* document; so we skip that here until if/when we transition it to use a better approach
*/
@BootstrapServiceRegistry(
javaServices = @JavaService(
role = AdditionalMappingContributor.class,
impl = AdditionalMappingContributorTests.AdditionalMappingContributorImpl.class
)
)
@DomainModel( annotatedClasses = AdditionalMappingContributorTests.Entity1.class )
@SessionFactory
public class AdditionalMappingContributorTests {
@Test
void verifyClassContribution(DomainModelScope domainModelScope, SessionFactoryScope sessionFactoryScope) {
final PersistentClass binding = domainModelScope.getDomainModel().getEntityBinding( Entity2.class.getName() );
assertThat( binding ).isNotNull();
assertThat( binding.getIdentifierProperty() ).isNotNull();
assertThat( binding.getProperties() ).hasSize( 1 );
sessionFactoryScope.inTransaction( (session) -> {
final List<?> results = session.createSelectionQuery( "from Entity2" ).list();
assertThat( results ).hasSize( 0 );
} );
}
@Test
void verifyOrmXmlContribution(DomainModelScope domainModelScope, SessionFactoryScope sessionFactoryScope) {
final PersistentClass binding = domainModelScope.getDomainModel().getEntityBinding( Entity3.class.getName() );
assertThat( binding ).isNotNull();
assertThat( binding.getIdentifierProperty() ).isNotNull();
assertThat( binding.getProperties() ).hasSize( 1 );
sessionFactoryScope.inTransaction( (session) -> {
final List<?> results = session.createSelectionQuery( "from Entity3" ).list();
assertThat( results ).hasSize( 0 );
} );
}
@Entity( name = "Entity1" )
@Table( name = "Entity1" )
public static class Entity1 {
@Id
private Integer id;
@Basic
private String name;
private Entity1() {
// for use by Hibernate
}
public Entity1(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Entity( name = "Entity2" )
@Table( name = "Entity2" )
public static class Entity2 {
@Id
private Integer id;
@Basic
private String name;
private Entity2() {
// for use by Hibernate
}
public Entity2(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Entity( name = "Entity3" )
@Table( name = "Entity3" )
public static class Entity3 {
@Id
private Integer id;
@Basic
private String name;
private Entity3() {
// for use by Hibernate
}
public Entity3(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class AdditionalMappingContributorImpl implements AdditionalMappingContributor {
@Override
public void contribute(
AdditionalMappingContributions contributions,
InFlightMetadataCollector metadata,
ResourceStreamLocator resourceStreamLocator,
MetadataBuildingContext buildingContext) {
contributions.contributeEntity( Entity2.class );
try ( final InputStream stream = resourceStreamLocator.locateResourceStream( "mappings/intg/contributed-mapping.xml" ) ) {
contributions.contributeBinding( stream );
}
catch (IOException e) {
throw new RuntimeException( e );
}
}
}
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<entity-mappings xmlns="http://www.hibernate.org/xsd/orm/mapping" version="3.1">
<entity class="org.hibernate.orm.test.intg.AdditionalMappingContributorTests$Entity3">
<attributes>
<id name="id"/>
<basic name="name"/>
</attributes>
</entity>
</entity-mappings>

View File

@ -7,16 +7,12 @@
package org.hibernate.envers.boot.internal;
import org.hibernate.HibernateException;
import org.hibernate.boot.jaxb.Origin;
import org.hibernate.boot.jaxb.SourceType;
import org.hibernate.boot.jaxb.hbm.spi.JaxbHbmHibernateMapping;
import org.hibernate.boot.jaxb.internal.MappingBinder;
import org.hibernate.boot.ResourceStreamLocator;
import org.hibernate.boot.spi.AdditionalMappingContributions;
import org.hibernate.boot.spi.AdditionalMappingContributor;
import org.hibernate.boot.spi.InFlightMetadataCollector;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.boot.spi.MetadataBuildingOptions;
import org.hibernate.envers.configuration.internal.MappingCollector;
import org.hibernate.service.ServiceRegistry;
import static org.hibernate.cfg.AvailableSettings.XML_MAPPING_ENABLED;
@ -34,7 +30,7 @@ public class AdditionalMappingContributorImpl implements AdditionalMappingContri
public void contribute(
AdditionalMappingContributions contributions,
InFlightMetadataCollector metadata,
MappingBinder jaxbBinder,
ResourceStreamLocator resourceStreamLocator,
MetadataBuildingContext buildingContext) {
final MetadataBuildingOptions metadataBuildingOptions = metadata.getMetadataBuildingOptions();
final ServiceRegistry serviceRegistry = metadataBuildingOptions.getServiceRegistry();
@ -51,15 +47,6 @@ public class AdditionalMappingContributorImpl implements AdditionalMappingContri
+ "`; alternatively disable Hibernate Envers." );
}
final MappingCollector mappingCollector = new MappingCollector() {
private final Origin origin = new Origin( SourceType.OTHER, "envers" );
@Override
public void addDocument(JaxbHbmHibernateMapping mapping) {
contributions.contributeBinding( mapping, origin );
}
};
enversService.initialize( metadata, mappingCollector );
enversService.initialize( metadata, contributions::contributeBinding );
}
}