diff --git a/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/ClassHierarchyTest.java b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/ClassHierarchyTest.java new file mode 100644 index 0000000000000..a9dd211d283b9 --- /dev/null +++ b/extensions/hibernate-validator/deployment/src/test/java/io/quarkus/hibernate/validator/test/ClassHierarchyTest.java @@ -0,0 +1,63 @@ +package io.quarkus.hibernate.validator.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.AbstractCollection; + +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.validation.Validator; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.ByteArrayAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; + +public class ClassHierarchyTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(() -> { + JavaArchive javaArchive = ShrinkWrap.create(JavaArchive.class) + .addClass(Dto.class); + // Create an inner class with an incomplete hierarchy + try (DynamicType.Unloaded superClass = new ByteBuddy() + .subclass(Object.class) + .name("SuperClass") + .make(); + DynamicType.Unloaded outerClass = new ByteBuddy() + .subclass(superClass.getTypeDescription()) + .name("OuterClass") + .make(); + DynamicType.Unloaded innerClass = new ByteBuddy() + .subclass(AbstractCollection.class) + .innerTypeOf(outerClass.getTypeDescription()) + .name("InnerClass") + .make(); + DynamicType.Loaded innerLoad = innerClass.load(Thread.currentThread().getContextClassLoader())) { + javaArchive.add(new ByteArrayAsset(innerLoad.getBytes()), "InnerClass.class"); + } + return javaArchive; + }); + + @Inject + Validator validator; + + @Test + public void doNotFailWhenLoadingIncompleteClassHierarchy() { + assertThat(validator).isNotNull(); + } + + @Valid + public static class Dto { + String name; + + // InnerClass is a subclass with an incomplete hierarchy + @Valid + AbstractCollection items; + } +} diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java index 88b808fd14142..40429cc069436 100644 --- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/HibernateValidatorRecorder.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Set; @@ -76,6 +77,9 @@ public void created(BeanContainer container) { configuration.localeResolver(localeResolver); } + // Filter out classes with incomplete hierarchy + filterIncompleteClasses(classesToBeValidated); + configuration.builtinConstraints(detectedBuiltinConstraints) .initializeBeanMetaData(classesToBeValidated) // Locales, Locale ROOT means all locales in this setting. @@ -188,6 +192,22 @@ public void run() { } }); } + + /** + * Filter out classes with incomplete hierarchy + */ + private void filterIncompleteClasses(Set> classesToBeValidated) { + Iterator> iterator = classesToBeValidated.iterator(); + while (iterator.hasNext()) { + Class clazz = iterator.next(); + try { + // This should trigger a NoClassDefFoundError if the class has an incomplete hierarchy + clazz.getCanonicalName(); + } catch (NoClassDefFoundError e) { + iterator.remove(); + } + } + } }; return beanContainerListener;