Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arc - remove wildcard types from bean types of class-based beans #30447

Merged
merged 2 commits into from
Jan 20, 2023

Conversation

manovotn
Copy link
Contributor

A complementary fix to #30412 and #30415

I originally forgot to address a scenario where a class based bean was able to retain wildcard type as its bean type.
This is against CDI specification and should not occur (the TCK covers that as well).

@manovotn manovotn added the area/arc Issue related to ARC (dependency injection) label Jan 18, 2023
@manovotn manovotn requested review from Ladicek and mkouba January 18, 2023 14:03
@manovotn
Copy link
Contributor Author

The failures looks related. In fact, it seem that HV is maybe using bean types as a basic for validation.
Though I am a HV rookie so maybe @yrodiere might know more? Any pointers are appreciated.

@quarkus-bot

This comment has been minimized.

@yrodiere
Copy link
Member

yrodiere commented Jan 19, 2023

Hey @manovotn.

Hibernate Validator uses annotations on type parameters to configure value extractors (which can be CDI beans):

@Singleton
public static class SingletonContainerValueExtractor
implements ValueExtractor<Container<@ExtractedValue ?>> {
@Override
public void extractValues(Container<?> originalValue, ValueReceiver receiver) {
receiver.value("someName", originalValue.value);
}
}

It would seem that your change is somehow removing the wildcard and corresponding annotation in the code above, leading HV to not detect the value extractor, leading (later) to a failure because HV cannot extract values from a Container.

Does that help?

EDIT: Here's the full stacktrace of the failure, for future reference:

[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 3.251 s <<< FAILURE! - in io.quarkus.hibernate.validator.test.valueextractor.SingletonCustomValueExtractorTest
[ERROR] io.quarkus.hibernate.validator.test.valueextractor.SingletonCustomValueExtractorTest  Time elapsed: 3.251 s  <<< ERROR!
java.lang.ExceptionInInitializerError
        at java.base/java.lang.Class.forName0(Native Method)
        at java.base/java.lang.Class.forName(Class.java:467)
        at io.quarkus.runner.bootstrap.StartupActionImpl.run(StartupActionImpl.java:237)
        at io.quarkus.test.QuarkusUnitTest.beforeAll(QuarkusUnitTest.java:642)
        at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.lambda$invokeBeforeAllCallbacks$12(ClassBasedTestDescriptor.java:395)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeBeforeAllCallbacks(ClassBasedTestDescriptor.java:395)
        at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:211)
        at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.before(ClassBasedTestDescriptor.java:84)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:148)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
        at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
        at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
        at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
        at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
        at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
        at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
        at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
        at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
        at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
        at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
        at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
        at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
        at org.apache.maven.surefire.junitplatform.LazyLauncher.execute(LazyLauncher.java:55)
        at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.execute(JUnitPlatformProvider.java:223)
        at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invokeAllTests(JUnitPlatformProvider.java:175)
        at org.apache.maven.surefire.junitplatform.JUnitPlatformProvider.invoke(JUnitPlatformProvider.java:139)
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:456)
        at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:169)
        at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:595)
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:581)
Caused by: java.lang.RuntimeException: Failed to start quarkus
        at io.quarkus.runner.ApplicationImpl.<clinit>(Unknown Source)
        ... 47 more
Caused by: javax.validation.ConstraintDeclarationException: HV000197: No value extractor found for type parameter 'T' of type io.quarkus.hibernate.validator.test.valueextractor.Container.
        at org.hibernate.validator.internal.metadata.core.MetaConstraints.addValueExtractorDescriptorForTypeArgumentLocation(MetaConstraints.java:147)
        at org.hibernate.validator.internal.metadata.core.MetaConstraints.create(MetaConstraints.java:63)
        at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findTypeUseConstraints(AnnotationMetaDataProvider.java:805)
        at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findTypeArgumentsConstraints(AnnotationMetaDataProvider.java:775)
        at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findTypeAnnotationConstraints(AnnotationMetaDataProvider.java:608)
        at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.findPropertyMetaData(AnnotationMetaDataProvider.java:241)
        at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getFieldMetaData(AnnotationMetaDataProvider.java:229)
        at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.retrieveBeanConfiguration(AnnotationMetaDataProvider.java:129)
        at org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider.getBeanConfiguration(AnnotationMetaDataProvider.java:120)
        at org.hibernate.validator.internal.metadata.PredefinedScopeBeanMetaDataManager.getBeanConfigurationForHierarchy(PredefinedScopeBeanMetaDataManager.java:183)
        at org.hibernate.validator.internal.metadata.PredefinedScopeBeanMetaDataManager.createBeanMetaData(PredefinedScopeBeanMetaDataManager.java:150)
        at org.hibernate.validator.internal.metadata.PredefinedScopeBeanMetaDataManager.<init>(PredefinedScopeBeanMetaDataManager.java:100)
        at org.hibernate.validator.internal.engine.PredefinedScopeValidatorFactoryImpl.<init>(PredefinedScopeValidatorFactoryImpl.java:183)
        at org.hibernate.validator.PredefinedScopeHibernateValidator.buildValidatorFactory(PredefinedScopeHibernateValidator.java:42)
        at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.buildValidatorFactory(AbstractConfigurationImpl.java:433)
        at io.quarkus.hibernate.validator.runtime.HibernateValidatorRecorder$2.created(HibernateValidatorRecorder.java:163)
        at io.quarkus.arc.runtime.ArcRecorder.initBeanContainer(ArcRecorder.java:73)
        at io.quarkus.deployment.steps.ArcProcessor$generateResources844392269.deploy_0(Unknown Source)
        at io.quarkus.deployment.steps.ArcProcessor$generateResources844392269.deploy(Unknown Source)
        ... 48 more

@yrodiere
Copy link
Member

Oops, the link to the code was truncated. I edited my comment.

@yrodiere
Copy link
Member

Ah but wait, I found that in the HV code:

private static final TypeLiteral<ValueExtractor<?>> TYPE_LITERAL_VALUE_EXTRACTOR_WITH_WILDCARD = new TypeLiteral<ValueExtractor<?>>() {
};

for (ValueExtractor<?> valueExtractor : Arc.container().beanManager().createInstance()
// Apparently passing ValueExtractor.class
// won't match beans implementing ValueExtractor<NotAWildcard>,
// so we need a type literal here.
.select(TYPE_LITERAL_VALUE_EXTRACTOR_WITH_WILDCARD)) {
configuration.addValueExtractor(valueExtractor);
}

Maybe that hack is no longer necessary, and doing a simple request with ValueExtractor.class would work after your patch?

@yrodiere
Copy link
Member

yrodiere commented Jan 19, 2023

@manovotn Changing .select(TYPE_LITERAL_VALUE_EXTRACTOR_WITH_WILDCARD) to .select(ValueExtractor.class) doesn't seem to fix the problem. From what I can see, this bean:

 @Singleton 
 public static class SingletonContainerValueExtractor 
         implements ValueExtractor<Container<@ExtractedValue ?>> { 
  
     @Override 
     public void extractValues(Container<?> originalValue, ValueReceiver receiver) { 
         receiver.value("someName", originalValue.value); 
     } 
 } 

Is simply ignored and not added to the CDI context... At least I don't see it in Arc.container().beans. I suspect the type ValueExtractor was ignored for that bean, and thus it was removed?

Maybe you should adjust your code to not ignore bean supertypes that contain wildcards, but just erase them (i.e. register the raw supertype ValueExtractor for SingletonContainerValueExtractor above)?

@manovotn
Copy link
Contributor Author

@yrodiere according to CDI spec:

A parameterized type that contains a wildcard type parameter is not a legal bean type.

Remaining types should be preserved. There is a TCK test (and the test I added here with the Eagle type is the same) asserting a very similar scenario that you're showing where ValueExtractor would not be a valid bean type.

@yrodiere
Copy link
Member

yrodiere commented Jan 19, 2023

@manovotn Does that TCK test expect the 3 types to be Eagle, Bird and AnimalHolder (raw, not parameterized), or Eagle, Bird and Object?

If it's the former, that's exactly what I was asking for and it would work with HV.


If it's the latter, I don't really see the point of that limitation, since it would only remove a feature that users possibly rely on currently (and thus break backwards compatibility, by the way). Not sure we want to be that strict, at least not by default? Configuration flags to enable strict spec compliance come to mind.

Alternatively, I suppose we could enable type matching (.select()) to take into account more types than what Bean#getTypes returns. I.e. Bean#getTypes() would only return a subset (Eagle, Bird and Object) of all the bean types that we consider when doing matches (Eagle, Bird, AnimalHolder and Object). That particular TCK test would then pass, as far as I can see, and we would preserve backwards compatibility at least to some extent.

If we want to break compatibility instead, in the specific case of Hibernate Validator, we could use jandex to list all types extending ValueExtractor and retrieve those types explicitly. Which seems a shame to me considering that's usually CDI's job, but I guess if we don't have a choice...

@manovotn
Copy link
Contributor Author

@yrodiere the TCK test explicitly checks that AnimalHolder is not a bean type. See this line.

I also brought this up with @mkouba and @Ladicek to get some more CDI expertise on this but the conclusion is what I am telling you.

If it's the latter, I don't really see the point of that limitation...

That decision predates my engagement with the spec so I can only assume it's because of some typesafe resolution cases which would otherwise not work correctly.

Note that there has to be similar limitation in WFLY integration. Weld is for sure passing CDI TCKs so it will have the same "problem". I'd assume HV had to deal with it there as well?

Not sure we want to be that strict, at least not by default?

This was basically an oversight. We already did similar bean type removal for producer method/field we just forgot to apply this to class beans. With that in mind it's not as much of a breaking change as it is fixing an inconsistency of our behavior.

@yrodiere
Copy link
Member

yrodiere commented Jan 19, 2023

@yrodiere the TCK test explicitly checks that AnimalHolder is not a bean type. See this line.

It does not. It checks that AnimalHolder<?> is not a bean type. It says nothing about the raw type: that specific assertion would pass if AnimalHolder.class (the raw type) was in the list of bean types. Only the assertion that checks the number of bean types gives us a clue about that. Hence my question.

Note that there has to be similar limitation in WFLY integration. Weld is for sure passing CDI TCKs so it will have the same "problem". I'd assume HV had to deal with it there as well?

WildFly doesn't allow declaring ValueExtractors as CDI beans. That's a Quarkus-specific feature.

With that in mind it's not as much of a breaking change as it is fixing an inconsistency of our behavior.

It can be both.

On that note, I'll summon @gsmet to give his opinion: should I change HV to use Jandex to workaround this limitation we're introducing in Arc, or should we do something else?

@gsmet
Copy link
Member

gsmet commented Jan 19, 2023

Well, I can't say I'm excited about this...

But if you all are saying what we do is against spec, I suppose we need to go the Jandex way.

@manovotn
Copy link
Contributor Author

Note that this is related to #28558 which is a tracker issue for (eventual) CDI Lite compliance in Quarkus 3.
I expect we'll find a lot more cases of missing validation compared to CDI TCKs as we haven't really executed them up until this point and they cover fair amount of rare cases such as this one here.

It does not. It checks that AnymalHolder<?> is not a bean type. It says nothing about the raw type. Only the assertion that checks the number of bean types gives us a clue about that. Hence my question.

But that's how it works in CDI. If given type isn't a legal bean type, then you cannot use the raw type either.
See Typesafe resolution and Assignability of parameterized and raw types.

It can be both.

True, just pointing that out :)

@manovotn
Copy link
Contributor Author

Well, I can't say I'm excited about this...

But if you all are saying what we do is against spec, I suppose we need to go the Jandex way.

I don't mean to be the bad guy pushing this, I just saw and inconsistency and wanted to fix it, sorry @gsmet :-D

@Ladicek
Copy link
Contributor

Ladicek commented Jan 19, 2023

Just want to point out one thing: we do consider a "strict compliance" switch, which would be off by default (and we'd turn it on to pass the CDI TCK). As we go through the TCK tests, I'm sure we'll stumble upon cases like this and we'll need to add the switch. The problem is that such switch is global. If we relied on our default relaxed behavior in our extensions, the extensions would start to fail once the user turns the switch on (which they may do for various reasons). So I believe we'll need our extensions to work correctly in the strictly compliant mode as well.

@quarkus-bot quarkus-bot bot added the area/hibernate-validator Hibernate Validator label Jan 19, 2023
@yrodiere
Copy link
Member

The initial policy of not adhering strictly to specs was not only about user experience, it was also about freeing up some time on our end, because those spec-mandated behaviors that we find uninteresting or even detrimental would not have to be implemented/dealt with. If extensions need to be compatible with both compliant and non-compliant mode, we do have to deal with those behaviors, and that means spending time that we won't spend innovating.

Worse, having to be compatible with both compliant and non-compliant mode is actually more effort than having to just be compatible with spec-compliant mode.

I understand where y'all are coming from, and I know complaining won't change anything. That won't stop me from being unhappy about it, though :)

Anyway, I just pushed a commit that sidesteps the problem.

@Ladicek
Copy link
Contributor

Ladicek commented Jan 19, 2023

Good point there. I was mainly thinking that the strictly compliant mode would disallow shortcuts such as omitting @Inject when a qualifier is present, or omitting @Produces when a qualifier is present. In such cases, achieving compatibility with both modes is pretty straightforward. I certainly would not want the two modes to have differences in how resolution or lookup behaves, that's a recipe for disaster.

@manovotn
Copy link
Contributor Author

I understand where y'all are coming from, and I know complaining won't change anything. That won't stop me from being unhappy about it, though :)

Understood. I appreciate the workaround you implemented :)
And thanks @Ladicek for chiming in as well.

@quarkus-bot

This comment has been minimized.

@quarkus-bot

This comment has been minimized.

@quarkus-bot
Copy link

quarkus-bot bot commented Jan 20, 2023

Failing Jobs - Building f5cf529

Status Name Step Failures Logs Raw logs
Devtools Tests - JDK 11 Build Failures Logs Raw logs
Devtools Tests - JDK 11 Windows Build ⚠️ Check → Logs Raw logs
Devtools Tests - JDK 17 Build Failures Logs Raw logs

Full information is available in the Build summary check run.

Failures

⚙️ Devtools Tests - JDK 11 #

- Failing: integration-tests/devtools 

📦 integration-tests/devtools

io.quarkus.devtools.codestarts.quarkus.HibernateOrmCodestartTest.testContent line 23 - More details - Source on GitHub

java.lang.AssertionError: 
[Snapshot is not matching (use -Dsnap to update it automatically): HibernateOrmCodestartTest/testContent/src_main_kotlin_ilove_quark_us_MyKotlinEntity.kt] 
Path:

io.quarkus.devtools.codestarts.quarkus.HibernateOrmPanacheKotlinCodestartTest.testContent line 23 - More details - Source on GitHub

java.lang.AssertionError: 
[Snapshot is not matching (use -Dsnap to update it automatically): HibernateOrmPanacheKotlinCodestartTest/testContent/src_main_kotlin_ilove_quark_us_MyKotlinEntity.kt] 
Path:

⚙️ Devtools Tests - JDK 17 #

- Failing: integration-tests/devtools 

📦 integration-tests/devtools

io.quarkus.devtools.codestarts.quarkus.HibernateOrmCodestartTest.testContent line 23 - More details - Source on GitHub

java.lang.AssertionError: 
[Snapshot is not matching (use -Dsnap to update it automatically): HibernateOrmCodestartTest/testContent/src_main_kotlin_ilove_quark_us_MyKotlinEntity.kt] 
Path:

io.quarkus.devtools.codestarts.quarkus.HibernateOrmPanacheKotlinCodestartTest.testContent line 23 - More details - Source on GitHub

java.lang.AssertionError: 
[Snapshot is not matching (use -Dsnap to update it automatically): HibernateOrmPanacheKotlinCodestartTest/testContent/src_main_kotlin_ilove_quark_us_MyKotlinEntity.kt] 
Path:

@yrodiere
Copy link
Member

The test failures are unrelated, see #30501

@manovotn
Copy link
Contributor Author

Good to know, I only re-ran it since I was surprised it didn't show up earlier.

We still need some review on this to get it on - @mkouba @Ladicek ? :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/arc Issue related to ARC (dependency injection) area/hibernate-validator Hibernate Validator
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants