Skip to content

Commit a9cad58

Browse files
authored
Merge pull request #48639 from yrodiere/i48631
Automatically register custom value generators for reflection
2 parents da01a0c + 5bf4f4c commit a9cad58

14 files changed

+232
-1
lines changed

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/ClassNames.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ private static DotName createConstant(String fqcn) {
6262
public static final DotName HIBERNATE_CACHE = createConstant("org.hibernate.Cache");
6363
public static final DotName PERSISTENCE_UNIT_UTIL = createConstant("jakarta.persistence.PersistenceUnitUtil");
6464

65+
public static final DotName GENERIC_GENERATOR = createConstant("org.hibernate.annotations.GenericGenerator");
66+
public static final DotName ID_GENERATOR_TYPE = createConstant("org.hibernate.annotations.IdGeneratorType");
67+
public static final DotName VALUE_GENERATION_TYPE = createConstant("org.hibernate.annotations.ValueGenerationType");
68+
6569
public static final DotName INTERCEPTOR = createConstant("org.hibernate.Interceptor");
6670
public static final DotName STATEMENT_INSPECTOR = createConstant("org.hibernate.resource.jdbc.spi.StatementInspector");
6771
public static final DotName FORMAT_MAPPER = createConstant("org.hibernate.type.format.FormatMapper");

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/GraalVMFeatures.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ NativeImageFeatureBuildItem staticNativeImageFeature() {
2626

2727
// TODO try to limit registration to those that are actually needed, based on configuration + mapping.
2828
// https://github.com/quarkusio/quarkus/pull/32433#issuecomment-1497615958
29+
// See also io.quarkus.hibernate.orm.deployment.JpaJandexScavenger.enlistClassReferences for
30+
// the beginning of a solution (which only handles custom types, not references by name such as 'sequence').
2931
@BuildStep
3032
ReflectiveClassBuildItem registerGeneratorAndOptimizerClassesForReflections() {
3133
return ReflectiveClassBuildItem

extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ public JpaModelBuildItem discoverModelAndRegisterForReflection() throws BuildExc
9494
enlistPotentialCdiBeanClasses(collector, annotation);
9595
}
9696

97+
enlistPotentialClassReferences(collector, ClassNames.GENERIC_GENERATOR, "type", "strategy");
98+
enlistPotentialClassReferences(collector, ClassNames.ID_GENERATOR_TYPE, "value");
99+
enlistPotentialClassReferences(collector, ClassNames.VALUE_GENERATION_TYPE, "generatedBy");
100+
97101
for (JpaModelPersistenceUnitContributionBuildItem persistenceUnitContribution : persistenceUnitContributions) {
98102
enlistExplicitMappings(collector, persistenceUnitContribution);
99103
}
@@ -214,10 +218,16 @@ private void enlistOrmXmlMappingManagedClass(Collector collector, String package
214218
String nodeName) {
215219
String name = safeGetClassName(packagePrefix, managed, nodeName);
216220
enlistExplicitClass(collector, name);
217-
if (managed instanceof JaxbEntity) {
221+
if (managed instanceof JaxbEntity entity) {
218222
// The call to 'enlistExplicitClass' above may not
219223
// detect that this class is an entity if it is not annotated
220224
collector.entityTypes.add(name);
225+
226+
// Generators may be instantiated reflectively
227+
if (entity.getGenericGenerator() != null) {
228+
var generator = entity.getGenericGenerator();
229+
enlistPotentialClassReference(collector, generator == null ? null : generator.getClazz());
230+
}
221231
}
222232

223233
enlistOrmXmlMappingListeners(collector, packagePrefix, managed.getEntityListenerContainer());
@@ -437,6 +447,47 @@ private void enlistPotentialCdiBeanClasses(Collector collector, DotName dotName)
437447
}
438448
}
439449

450+
private void enlistPotentialClassReferences(Collector collector, DotName dotName, String... referenceAttributes) {
451+
Collection<AnnotationInstance> jpaAnnotations = index.getAnnotations(dotName);
452+
453+
if (jpaAnnotations == null) {
454+
return;
455+
}
456+
457+
for (AnnotationInstance annotation : jpaAnnotations) {
458+
for (String referenceAttribute : referenceAttributes) {
459+
var referenceValue = annotation.value(referenceAttribute);
460+
if (referenceValue == null) {
461+
continue;
462+
}
463+
String reference = switch (referenceValue.kind()) {
464+
case CLASS -> referenceValue.asClass().name().toString();
465+
case STRING -> {
466+
String stringRef = referenceValue.asString();
467+
if (stringRef.isEmpty() || index.getClassByName(stringRef) == null) {
468+
// No reference, or reference to a built-in strategy name like 'sequence'
469+
// (which we can't resolve here and handle through GraalVMFeatures.registerGeneratorAndOptimizerClassesForReflections)
470+
yield null;
471+
}
472+
yield stringRef;
473+
}
474+
default -> null;
475+
};
476+
enlistPotentialClassReference(collector, reference);
477+
}
478+
}
479+
}
480+
481+
/**
482+
* Add the class to the reflective list with only constructor and method access.
483+
*/
484+
private void enlistPotentialClassReference(Collector collector, String reference) {
485+
if (reference == null) {
486+
return;
487+
}
488+
collector.javaTypes.add(reference);
489+
}
490+
440491
/**
441492
* Add the class to the reflective list with full method and field access.
442493
* Add the superclasses recursively as well as the interfaces.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.quarkus.it.jpa.generatedvalue;
2+
3+
import jakarta.persistence.Entity;
4+
import jakarta.persistence.GeneratedValue;
5+
import jakarta.persistence.Id;
6+
7+
import org.hibernate.annotations.GenericGenerator;
8+
9+
@Entity
10+
public class EntityWithCustomGenericGeneratorReferencedAsClass {
11+
@Id
12+
@GeneratedValue(generator = "referenced-as-class")
13+
@GenericGenerator(name = "referenced-as-class", type = MyCustomGenericGeneratorReferencedAsClass.class)
14+
public String id;
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.quarkus.it.jpa.generatedvalue;
2+
3+
import jakarta.persistence.Entity;
4+
import jakarta.persistence.GeneratedValue;
5+
import jakarta.persistence.Id;
6+
7+
import org.hibernate.annotations.GenericGenerator;
8+
9+
@Entity
10+
public class EntityWithCustomGenericGeneratorReferencedAsClassName {
11+
@Id
12+
@GeneratedValue(generator = "referenced-as-class-name")
13+
@GenericGenerator(name = "referenced-as-class-name", strategy = "io.quarkus.it.jpa.generatedvalue.MyCustomGenericGeneratorReferencedAsClassName")
14+
public String id;
15+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.quarkus.it.jpa.generatedvalue;
2+
3+
import jakarta.persistence.Entity;
4+
import jakarta.persistence.GeneratedValue;
5+
import jakarta.persistence.Id;
6+
7+
@Entity
8+
public class EntityWithCustomIdGeneratorType {
9+
@Id
10+
@GeneratedValue
11+
@MyCustomIdGeneratorAnnotation
12+
public String id;
13+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.quarkus.it.jpa.generatedvalue;
2+
3+
import jakarta.persistence.Entity;
4+
import jakarta.persistence.GeneratedValue;
5+
import jakarta.persistence.Id;
6+
7+
@Entity
8+
public class EntityWithCustomValueGeneratorType {
9+
@Id
10+
@GeneratedValue
11+
public Integer id;
12+
13+
@MyCustomValueGeneratorAnnotation
14+
public String customGenerated;
15+
}

integration-tests/jpa/src/main/java/io/quarkus/it/jpa/generatedvalue/GeneratedValueResource.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,30 @@ public String test() throws Exception {
4343
e -> assertThat(e.generated).isNotNull(),
4444
e -> assertThat(e.generatedColumn).isNotNull());
4545

46+
var entity2 = new EntityWithCustomIdGeneratorType();
47+
assertThat(entity2.id).isNull();
48+
em.persist(entity2);
49+
em.flush();
50+
assertThat(entity2.id).isEqualTo(MyCustomIdGenerator.STUB_VALUE);
51+
52+
var entity3 = new EntityWithCustomValueGeneratorType();
53+
assertThat(entity3.customGenerated).isNull();
54+
em.persist(entity3);
55+
em.flush();
56+
assertThat(entity3.customGenerated).isEqualTo(MyCustomValueGenerator.STUB_VALUE);
57+
58+
var entity4 = new EntityWithCustomGenericGeneratorReferencedAsClass();
59+
assertThat(entity4.id).isNull();
60+
em.persist(entity4);
61+
em.flush();
62+
assertThat(entity4.id).isEqualTo(MyCustomGenericGeneratorReferencedAsClass.STUB_VALUE);
63+
64+
var entity5 = new EntityWithCustomGenericGeneratorReferencedAsClassName();
65+
assertThat(entity5.id).isNull();
66+
em.persist(entity5);
67+
em.flush();
68+
assertThat(entity5.id).isEqualTo(MyCustomGenericGeneratorReferencedAsClassName.STUB_VALUE);
69+
4670
return "OK";
4771
}
4872
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.quarkus.it.jpa.generatedvalue;
2+
3+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
4+
import org.hibernate.id.IdentifierGenerator;
5+
6+
public class MyCustomGenericGeneratorReferencedAsClass implements IdentifierGenerator {
7+
public static String STUB_VALUE = MyCustomGenericGeneratorReferencedAsClass.class.getName() + "_STUB_VALUE";
8+
9+
@Override
10+
public Object generate(SharedSessionContractImplementor session, Object object) {
11+
return STUB_VALUE;
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.quarkus.it.jpa.generatedvalue;
2+
3+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
4+
import org.hibernate.id.IdentifierGenerator;
5+
6+
public class MyCustomGenericGeneratorReferencedAsClassName implements IdentifierGenerator {
7+
public static String STUB_VALUE = MyCustomGenericGeneratorReferencedAsClassName.class.getName() + "_STUB_VALUE";
8+
9+
@Override
10+
public Object generate(SharedSessionContractImplementor session, Object object) {
11+
return STUB_VALUE;
12+
}
13+
}

0 commit comments

Comments
 (0)