Skip to content

Commit

Permalink
HHH-17504 - Ongoing JPA 32 work
Browse files Browse the repository at this point in the history
HHH-17350 - Work on hibernate-models, XSD and JAXB
HHH-16114 - Improve boot metamodel binding
HHH-15996 - Develop an abstraction for Annotation in annotation processing
HHH-16012 - Develop an abstraction for domain model Class refs
HHH-15997 - Support for dynamic models in orm.xml
HHH-15698 - Support for entity-name in mapping.xsd
  • Loading branch information
sebersole committed Dec 6, 2023
1 parent f246394 commit e755c74
Show file tree
Hide file tree
Showing 15 changed files with 548 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.DependantValue;
import org.hibernate.mapping.Formula;
import org.hibernate.mapping.Table;
import org.hibernate.models.spi.AnnotationDescriptor;
import org.hibernate.models.spi.AnnotationUsage;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.hibernate.mapping.BasicValue;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Subclass;
import org.hibernate.mapping.Table;
import org.hibernate.models.ModelsException;
import org.hibernate.models.spi.AnnotationUsage;
Expand Down Expand Up @@ -506,6 +507,8 @@ private static <A extends Annotation> void applyCustomSql(
} );
}

public abstract RootEntityBinding getRootEntityBinding();

@FunctionalInterface
private interface PrimaryCustomSqlInjector {
void injectCustomSql(PersistentClass persistentClass, String sql, boolean callable, ExecuteUpdateResultCheckStyle checkStyle);
Expand All @@ -532,16 +535,6 @@ private static void applySynchronizedTableNames(
names.forEach( persistentClass::addSynchronizedTable );
}

protected void processSecondaryTables(TableReference primaryTableReference) {
TableHelper.bindSecondaryTables(
this,
primaryTableReference,
bindingOptions,
bindingState,
bindingContext
);
}

protected void prepareSubclassBindings() {
getTypeMetadata().forEachSubType( (subType) -> {
if ( subType instanceof EntityTypeMetadata entityTypeMetadata ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
*/
package org.hibernate.boot.models.bind.internal;

import java.util.ArrayList;
import java.util.List;

import org.hibernate.boot.models.bind.spi.BindingContext;
import org.hibernate.boot.models.bind.spi.BindingOptions;
import org.hibernate.boot.models.bind.spi.BindingState;
Expand Down Expand Up @@ -33,9 +36,12 @@
*
* @author Steve Ebersole
*/
public class IdentifierBinding extends Binding {
public class IdentifierBinding extends Binding implements Observable<KeyValue> {
private final KeyValue keyValue;

private boolean resolved;
private List<ResolutionCallback<KeyValue>> resolutionCallbacks;

public IdentifierBinding(
RootEntityBinding rootEntityBinding,
EntityTypeMetadata entityTypeMetadata,
Expand All @@ -44,7 +50,7 @@ public IdentifierBinding(
BindingContext context) {
super( options, state, context );

this.keyValue = prepareMappingValue( rootEntityBinding, entityTypeMetadata, options, state, context );
this.keyValue = prepareMappingValue( rootEntityBinding, entityTypeMetadata, this, options, state, context );
}

public KeyValue getValue() {
Expand All @@ -59,6 +65,7 @@ public KeyValue getBinding() {
private static KeyValue prepareMappingValue(
RootEntityBinding rootEntityBinding,
EntityTypeMetadata entityTypeMetadata,
IdentifierBinding binding,
BindingOptions options,
BindingState state,
BindingContext context) {
Expand All @@ -68,21 +75,21 @@ private static KeyValue prepareMappingValue(
final Table table = rootClass.getTable();

if ( idMapping instanceof BasicKeyMapping basicKeyMapping ) {
return prepareBasicIdentifier( basicKeyMapping, table, rootClass, options, state, context );
return prepareBasicIdentifier( basicKeyMapping, table, rootClass, binding, options, state, context );
}
else if ( idMapping instanceof AggregatedKeyMapping aggregatedKeyMapping ) {
return bindAggregatedIdentifier( aggregatedKeyMapping, table, rootClass, options, state, context );
}
else {
return bindNonAggregatedIdentifier( (NonAggregatedKeyMapping) idMapping, table, rootClass, options, state, context );
}

}

private static KeyValue prepareBasicIdentifier(
BasicKeyMapping basicKeyMapping,
Table table,
RootClass rootClass,
IdentifierBinding binding,
BindingOptions options,
BindingState state,
BindingContext context) {
Expand Down Expand Up @@ -118,6 +125,8 @@ private static KeyValue prepareBasicIdentifier(
BasicValueHelper.bindJdbcType( idAttributeMember, idValue, options, state, context );
BasicValueHelper.bindNationalized( idAttributeMember, idValue, options, state, context );

binding.resolve();

return idValue;
}

Expand Down Expand Up @@ -150,4 +159,24 @@ private static KeyValue bindNonAggregatedIdentifier(
BindingContext context) {
throw new UnsupportedOperationException( "Not yet implemented" );
}


@Override
public void whenResolved(ResolutionCallback<KeyValue> callback) {
if ( resolved ) {
callback.handleResolution( keyValue );
return;
}

if ( resolutionCallbacks == null ) {
resolutionCallbacks = new ArrayList<>();
}
resolutionCallbacks.add( callback );
}

public void resolve() {
ObservableHelper.processCallbacks( keyValue, resolutionCallbacks );
resolutionCallbacks = null;
resolved = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* 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.models.bind.internal;

import java.util.List;
import java.util.Locale;

import org.hibernate.MappingException;
import org.hibernate.boot.spi.MetadataBuildingContext;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.DependantValue;
import org.hibernate.mapping.JoinedSubclass;
import org.hibernate.mapping.KeyValue;
import org.hibernate.models.spi.AnnotationUsage;
import org.hibernate.models.spi.ClassDetails;

import jakarta.persistence.ForeignKey;
import jakarta.persistence.PrimaryKeyJoinColumn;

/**
* @author Steve Ebersole
*/
public record JoinedSubclassKeyHandler(
ClassDetails subclassDetails,
JoinedSubclass subclass,
MetadataBuildingContext buildingContext) implements ResolutionCallback<KeyValue> {

@Override
public boolean handleResolution(KeyValue targetKeyValue) {
final List<AnnotationUsage<PrimaryKeyJoinColumn>> pkJoinColumns = subclassDetails.getRepeatedAnnotationUsages( PrimaryKeyJoinColumn.class );
final DependantValue fkValue = new DependantValue( buildingContext, subclass.getTable(), targetKeyValue );

if ( CollectionHelper.isEmpty( pkJoinColumns ) ) {
handleImplicitJoinColumns( targetKeyValue, fkValue );
}
else {
handleExplicitJoinColumns( pkJoinColumns, targetKeyValue, fkValue );
}

final AnnotationUsage<ForeignKey> fkAnn = subclassDetails.getAnnotationUsage( ForeignKey.class );

final String foreignKeyName = fkAnn == null
// todo : generate the name here - this *is* equiv to legacy second pass
? ""
: fkAnn.getString( "name" );
final String foreignKeyDefinition = fkAnn == null
? ""
: fkAnn.getString( "foreignKeyDefinition" );

final org.hibernate.mapping.ForeignKey foreignKey = subclass.getTable().createForeignKey(
foreignKeyName,
fkValue.getColumns(),
subclass.getRootClass().getEntityName(),
foreignKeyDefinition,
targetKeyValue.getColumns()
);
foreignKey.setReferencedTable( subclass.getRootTable() );

return true;
}

private void handleImplicitJoinColumns(KeyValue targetKeyValue, DependantValue fkValue) {
targetKeyValue.getColumns().forEach( (column) -> {
final Column fkColumn = column.clone();
subclass.getTable().getPrimaryKey().addColumn( fkColumn );
fkValue.addColumn( fkColumn );
} );
}

private void handleExplicitJoinColumns(
List<AnnotationUsage<PrimaryKeyJoinColumn>> pkJoinColumns,
KeyValue targetKeyValue,
DependantValue fkValue) {
for ( int i = 0; i < targetKeyValue.getColumnSpan(); i++ ) {
final Column targetColumn = targetKeyValue.getColumns().get( i );
final var joinColumnAnn = resolveMatchingJoinColumnAnn(
targetColumn,
pkJoinColumns,
i
);

final Column keyColumn = ColumnHelper.bindColumn( joinColumnAnn, targetColumn::getName, true, false );
subclass().getTable().getPrimaryKey().addColumn( keyColumn );
fkValue.addColumn( keyColumn );
}
}

private AnnotationUsage<PrimaryKeyJoinColumn> resolveMatchingJoinColumnAnn(
Column targetPkColumn,
List<AnnotationUsage<PrimaryKeyJoinColumn>> keyJoinColumns,
int pkColumnPosition) {
for ( int j = 0; j < keyJoinColumns.size(); j++ ) {
final var keyJoinColumn = keyJoinColumns.get( j );
final String name = keyJoinColumn.getString( "name" );
final String referencedColumnName = keyJoinColumn.getString( "referencedColumnName" );
if ( StringHelper.isEmpty( name ) && StringHelper.isEmpty( referencedColumnName ) ) {
// assume positional match
// todo : is this correct? the only other option is to throw an exception
if ( j == pkColumnPosition ) {
return keyJoinColumn;
}
}
else if ( StringHelper.isNotEmpty( referencedColumnName ) ) {
if ( targetPkColumn.getName().equals( referencedColumnName ) ) {
return keyJoinColumn;
}
}
else {
assert StringHelper.isNotEmpty( name );
if ( targetPkColumn.getName().equals( name ) ) {
return keyJoinColumn;
}
}
}

throw new MappingException(
String.format(
Locale.ROOT,
"Unable to match primary key column [%s] to any PrimaryKeyJoinColumn - %s",
targetPkColumn.getName(),
subclass().getEntityName()
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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.models.bind.internal;

/**
* @author Steve Ebersole
*/
public interface Observable<T> {
void whenResolved(ResolutionCallback<T> callback);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* 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.models.bind.internal;

import java.util.Iterator;
import java.util.List;

import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.models.ModelsException;

import static org.hibernate.boot.models.bind.ModelBindingLogging.MODEL_BINDING_LOGGER;

/**
* @author Steve Ebersole
*/
public class ObservableHelper {
public static <T> void processCallbacks(
T resolvedInstance,
List<? extends ResolutionCallback<T>> callbacks) {
processCallbacks( resolvedInstance, callbacks, false );
}

public static <T> void processCallbacks(
T resolvedInstance,
List<? extends ResolutionCallback<T>> callbacks,
boolean allowUnresolved) {
if ( CollectionHelper.isEmpty( callbacks ) ) {
return;
}

int processedCount = 0;
final Iterator<? extends ResolutionCallback<T>> secondPassItr = callbacks.iterator();
while ( secondPassItr.hasNext() ) {
final ResolutionCallback<T> callback = secondPassItr.next();
try {
final boolean success = callback.handleResolution( resolvedInstance );
if ( success ) {
processedCount++;
secondPassItr.remove();
}
}
catch (Exception e) {
// todo : handle cases where the caught exception is a non-transient, invariant condition
// indicating to immediately stop the processing and throw an error (either the original
// or a new one.
MODEL_BINDING_LOGGER.debug( "Error processing second pass", e );
}
}

if ( !callbacks.isEmpty() ) {
if ( processedCount == 0 ) {
// there are callbacks in the queue, but we were not able to
// successfully process any of them. this is a non-changing
// error condition - throw an exception, unless `allowUnresolved` == true
if ( !allowUnresolved ) {
throw new ModelsException( "Unable to process second-pass list" );
}
}

processCallbacks( resolvedInstance, callbacks );
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.models.bind.internal;

/**
* Provides callback notification when an object of interest is
* fully resolved and all of its state available.
*
* @author Steve Ebersole
*/
@FunctionalInterface
public interface ResolutionCallback<T> {
/**
* Callback to use the fully resolved {@code resolvedThing}
*
* @param resolvedThing The resolved object of interest
*
* @return {@code true} if processing was successful; {@code false} otherwise
*/
boolean handleResolution(T resolvedThing);
}

0 comments on commit e755c74

Please sign in to comment.