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

Broken ParamConverterProvider ordering in 2.26 #3670

Closed
jerseyrobot opened this issue Sep 14, 2017 · 14 comments
Closed

Broken ParamConverterProvider ordering in 2.26 #3670

jerseyrobot opened this issue Sep 14, 2017 · 14 comments
Milestone

Comments

@jerseyrobot
Copy link
Contributor

It looks like there are a few issues regarding provider ordering in 2.26. Although I think older versions are also affected.

I created a minimal sample app to reproduce the issue: https://github.com/chkal/jersey-prio

Basically I'm trying to register a custom ParamConverter which overrides the default converter for the data type java.lang.Integer.

First issue:

The first issue is that whether the custom provider or the default provider is used randomly changes between deployments. That's caused by the use of a HashSet instead of a LinkedHashSet in ParamConverterFactory. My pull request #3669 should fix this.

Second issue

The second problem is more difficult to reason about. After merging #3669 you will see that Jersey will always pick the default provider and ignores the custom provider deployed with the app.

I'll try to explain what I debugged so far. Actually the code in the ParamConverterFactory tries to handle the case of custom providers overriding default providers correctly:

https://github.com/jersey/jersey/blob/4ecf20c7585abfe1bd2c312d890943e884a9eb3b/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/ParamConverterFactory.java#L74-L81

The weird thing is that providers deployed with the app always end up in the providers set in the constructor and NOT in the customProviders set. The ParamConverterFactory is created here:

https://github.com/jersey/jersey/blob/4ecf20c7585abfe1bd2c312d890943e884a9eb3b/core-server/src/main/java/org/glassfish/jersey/server/internal/inject/ParamExtractorConfigurator.java#L68-L71

If you dig deeper into the Provider.getProviders() and Provider.getCustomProviders() code, you will see that getCustomProviders() will look for providers with a qualifier Custom. I'm not sure under which circumstances providers get this qualifier.

Also noteworthy: Only getCustomProviders() seems to apply the ordering algorithm specified in JAX-RS 2.1 (see jax-rs/api#538). As custom providers aren't found by this method, the ordering algorithm isn't applied at all.

I hope this description helps to debug the problem. Let me know if there is anything I can do to help with this.

@jerseyrobot
Copy link
Contributor Author

@jerseyrobot jerseyrobot added this to the 2.27 milestone Apr 20, 2018
chkal added a commit to chkal/jersey that referenced this issue Oct 3, 2018
…clipse-ee4j#3670)

Signed-off-by: Christian Kaltepoth <christian@kaltepoth.de>
@chkal
Copy link
Contributor

chkal commented Oct 3, 2018

Hi all. As open merge requests haven't been migrated from the jersey/jersey repository, I create a new pull request #3955 which contains the changes from jersey/jersey#3669. This pull request fixes the first issue described above.

It would be great to get some feedback from the Jersey team regarding the second issue.

@jansupol
Copy link
Contributor

jansupol commented Oct 3, 2018

Thanks. As there historically were PRs in Jersey (and other projects) that were years old and possibly were no longer valid, the decision was not to transfer them. PR needed by the community should have been recreated in the new repos.

@chkal
Copy link
Contributor

chkal commented Oct 3, 2018

@jansupol Sure, starting from the scratch makes sense, especially if there are many old pull request!

Any chance that somebody could take a look at the "second issue" described above? I'm pretty sure that this is a compatibility issue regarding the @Priority ordering requirement for providers which has been introduced in JAX-RS 2.1. I would love to hear other thoughts on this.

jansupol pushed a commit that referenced this issue Oct 4, 2018
…3670) (#3955)

Signed-off-by: Christian Kaltepoth <christian@kaltepoth.de>
@jansupol
Copy link
Contributor

jansupol commented Feb 6, 2019

@chkal What providers do you describe here? Is it related to any provider ordering or is it ParamConverterProvider specific only? Jersey 2.26 changed the behaviour of @Priority, previous versions could behave differently.

@chkal
Copy link
Contributor

chkal commented Feb 8, 2019

@jansupol I ran into issues with both ParamConverterProvider and ExceptionMapper. In both cases one provider was packaged in WEB-INF/classes and the other one in a JAR placed in WEB-INF/lib (or was part of Jersey itself). So maybe this is the cause for the weird behavior described above!?

@chkal
Copy link
Contributor

chkal commented Feb 10, 2019

@jansupol I spent some more time debugging this issue. I'm still able to reproduce it with Eclipse Glassfish 5.1.0 which includes Eclipse Jersey 2.28. See the following description for more details.

Jersey looks up providers (like ExceptionMapper in my case) like this:

Providers.getAllServiceHolders(injectionManager, ExceptionMapper.class);

This lookup method works like this:

public static <T> Collection<ServiceHolder<T>> getAllServiceHolders(InjectionManager injectionManager, Class<T> contract) {
List<ServiceHolder<T>> providers = getServiceHolders(injectionManager,
contract,
Comparator.comparingInt(Providers::getPriority),
CustomAnnotationLiteral.INSTANCE);
providers.addAll(getServiceHolders(injectionManager, contract));
LinkedHashSet<ServiceHolder<T>> providersSet = new LinkedHashSet<>();
for (ServiceHolder<T> provider : providers) {
if (!providersSet.contains(provider)) {
providersSet.add(provider);
}
}
return providersSet;
}

As you see, this method actually performs two lookups:

List<ServiceHolder<T>> providers = getServiceHolders(injectionManager,
contract,
Comparator.comparingInt(Providers::getPriority),
CustomAnnotationLiteral.INSTANCE);

This code looks up provider implementations having the Custom qualifier. Please note that the supplied comparator will order the matching providers correctly according to their priority.

Then there is a second lookup:

providers.addAll(getServiceHolders(injectionManager, contract));

My understanding is, that this lookup adds all other providers (without the Custom qualifier) to the list. Please note that these providers are NOT ordered according to their priority.

In my case I'm having two ExceptionMapper implementations for the same exception type. One implementation is located in WEB-INF/classes, the other one in a library located in WEB-INF/lib.

The interesting fact is that both are NOT found in the first lookup (which would order them correctly), but instead found in the second lookup (which doesn't do any ordering).

I'm not familiar with how the component model and discovery works for Jersey in detail, but to me it looks like my implementations are not qualified with Custom and therefore treated as Jersey-internal providers which doesn't seem to get ordered correctly according to @Priority.

Sorry for my lengthy description, but I want to share as much of my findings as possible. Does this help in any way? Any idea what is causing this? Any hint on how to continue with this?

@chkal
Copy link
Contributor

chkal commented Feb 21, 2019

I'm currently working on finishing the TCK for JSR-371 and I'm getting more and tests failing on Glassfish because of this issue. Is there any chance that somebody from the Jersey team could have a look at my findings posted in the last comment and share some thoughts?

@jansupol jansupol added the 2.29 label Feb 25, 2019
@senivam
Copy link
Contributor

senivam commented Mar 1, 2019

for now I've discovered that the only difference which makes it work (on GF) is removing the root / from the @ Path in MyResource. So, the resource looks like

`
@ Path("param/{value}")
@ValidateOnExecution(type = ExecutableType.NONE)
public class MyResource {

@PathParam("value")
private Integer value;

@GET
public String get() {
    return "Value injected via @PathParam: " + value;
}

}`

@chkal
Copy link
Contributor

chkal commented Mar 1, 2019

@senivam Thanks a lot for looking into this. Does this really change the behavior I described above (custom provider not qualified with @Custom)? I'm just asking because I was seeing very unreproducible behavior and sometimes even changing a log message (and therefore recompiling the class files) resulted in a different ordering.

@senivam
Copy link
Contributor

senivam commented Mar 6, 2019

For now, as I was looking into this more deeply, when you take clean Tomcat and add required dependencies to your reproducer, it works for the described case like a charm (independently from a value in the Path annotation).

When you rewrite the reproducer into Jersey's test and/or example it works as well.

Further more, the provider is located in correct Custom providers map (without any custom annotation). So, it comes the first in the ordering and all works.

But when you run the reproducer in Glassfish (5.1.0 or 5.0.1) where Jersey is bundled along with another modules, something brakes Jersey's behavior and Custom provider gets to the end of internal providers map which is wrong (does not depend on a value of the Path annotation).

So, I will continue investigation what causes this behavior.

senivam added a commit to senivam/jersey that referenced this issue Mar 6, 2019
Signed-off-by: Maxim Nesen <maxim.nesen@oracle.com>
@chkal
Copy link
Contributor

chkal commented Mar 6, 2019

Thanks a lot for the update and for working on this. This is really a weird behavior. But I'm happy to hear that you are able to reproduce it.

@senivam
Copy link
Contributor

senivam commented Mar 6, 2019

I suppose it is solved. I've found the reason. I've submited a PR for that whith your reproducer as integration test. If you do not mind to use your code inside Jersey...

@chkal
Copy link
Contributor

chkal commented Mar 7, 2019

Sure, feel free to use the code as you like. Thanks a lot for your help.

senivam added a commit to senivam/jersey that referenced this issue Mar 12, 2019
Signed-off-by: Maxim Nesen <maxim.nesen@oracle.com>
senivam added a commit that referenced this issue Mar 13, 2019
Signed-off-by: Maxim Nesen <maxim.nesen@oracle.com>
@senivam senivam closed this as completed Mar 13, 2019
@senivam senivam modified the milestones: 2.27, 2.29 Sep 12, 2019
This was referenced Oct 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants