Thank you, Pavel.
You are correct, I understand better now.
What I actually meant was that It's not necessary to provide an implementation by the dev, only pass an interface declaring what he expects to get. You are right that he wouldn’t write the interface.
I like this solution.
It enables to decouple an interface from an impl – for example, reactive libraries can agree to use a common interface (e.g. based on Reactive streams), making it possible to swap the impl if needed.
Cheers,
Ondrej
Od: Pavel Bucek
Odesláno:neděle 22. ledna 2017 12:05
Komu: jsr370-experts_at_jax-rs-spec.java.net
Předmět: [jax-rs-spec users] Re: JAX-RS 2.1 - work schedule
Hi Ondrej,
just a note:
For me it is important not to pass any implementation to rx() method, and the solution of .rx(CompletionStageRxInvoker.
class) meets that. The developer jut needs to write an empty interface, jut to define proper type conversions, which is nice (except that awful number of methods to override, but I have no energy now to try to propose something better to reduce the number :) )
The developer won't be writing any interface - it won't work if he will do that. He needs to use exactly the same interface which is provide as part of the "extension implementation", so for example if there is a library with support for "org.pavel.Pavelable<T>", it contains:
• org.pavel.PavelInvoker implements RxInvoker<Pavelable>
• org.pavel.PavelProvider implements RxInvokerProvider<PavelInvoker>
and the code will always look like:
Client client = ClientBuilder.newClient();
client.register(org.pavel.PavelableProvider.class);
Pavelable<Response> csResponse =
client.target("
http://oracle.com")
.request()
.rx(org.pavel.PavelableInvoker.class)
.get();
Simply because underlying client runtime does a lookup for provider for a class PavelableInvoker, which in this case would be something like:
public static class PavelableProvider implements RxInvokerProvider<PavelableInvoker> {
@Override
public boolean provides(Class<?> clazz) {
return Pavelable.class.equals(clazz);
}
@Override
public PavelableInvoker getRxInvoker(SyncInvoker syncInvoker, ExecutorService executorService) {
return //...;
}
}
(note the "provides" method).
hope the proposal is clearer now. Please let me know if that changed anything about how you like it :).
Best regards,
Pavel
On 20/01/2017 18:07, Ondrej Mihályi wrote:
Thanks for your honesty, Pavel.
I didn't mean to propose a final solution, just a proof that the approach suggested by me and Markus can work.
What I'm after is really the consistency - Java EE API is mostly about standardizing what should be done and not how. Therefore it is more natural for me to declare that I want to use CompletionStage, RxJava or whatever, rather than to define how to do this conversion by passing a converter each time I want to use it. Even more if the converter is meant to be provided by the implementation and not by users of the API.
I accept that my solution is not perfect, I just wanted to point out that the approach is possible.
I agree that the last suggestion by Santiago is the best solution so far. For me it is important not to pass any implementation to rx() method, and the solution of .rx(CompletionStageRxInvoker.class) meets that. The developer jut needs to write an empty interface, jut to define proper type conversions, which is nice (except that awful number of methods to override, but I have no energy now to try to propose something better to reduce the number :) )
Thanks for the discussion :)
Ondrej
2017-01-20 15:27 GMT+01:00 Pavel Bucek <pavel.bucek_at_oracle.com>:
Hi Ondrej,
I like your determination and consistency.
Unfortunately, I have to strongly disagree with your last proposal. GenericType is nothing more than a standardized hack, which has limited usability and should be avoided if possible. Here, we do have that option.
Ad limited usability: imagine that user of the API will wrap your code in generic method, replacing List<String> with T. Then the information passed to the rx method will be exactly CompletionStage<T>, which cannot be used by the implementation for selecting the correct provider. Also, the code is (no offense) quite hard to write and read - we don't want to force anyone to write "Foo<Bar<Baz<Qux>>>". (I always wanted to use "qux" somewhere!)
I believe that the last variant brought up by Santiago fits nicely with existing APIs and uses already established provider pattern. I'd appreciate if you could evaluate it, maybe try to use it and share your thoughts once again.
Thanks and regards,
Pavel
On 20/01/2017 12:56, Ondrej Mihályi wrote:
Pavel raised a valid point, but it can really be solved by the GenericType class, just moving the usage of the GenericType from method get to method rx.
Here is a compilable and type-safe solution:
client.target("remote/forecast/{destination}")
.resolveTemplate("destination", "mars")
.request()
.header("Rx-User", "Java8")
.rx(new GenericType<CompletionStage<List<String>>>())
.get();
with the new rx() method on Invocation.Builder defined simply like this:
public <T> RxInvoker<T> rx(GenericType<T> type);
You can play with the code in my fork here:
https://github.com/OndrejM/api/blob/reactive_provider_by_return_type/jaxrs-api/src/test/java/javax/ws/rs/core/RxClientTest.java#L105
However, this solution also provides cons:
- it is uglier at first sight (although GenericType is always necessary at some point sooner or later)
- once converted to CompletionStage with proper type, it's not possible to retrieve the generic Response object in a typesafe way (making the methods like get(Class<R>) and get(GenericType<R> redundant - but maybe it;s a good thing and simplifies the RxInvoker interface)
The ugly part can be simplified by any developer using a class that extends GenericType like this (the code in my fork already shows that):
class CompletionStageType<T> extends GenericType<CompletionStage<T>>
And I don't think that the second drawback matters - if interested in Response, it can be retrieved by
.rx(new GenericType<CompletionStage<Response>() {})
.get();
and converted later if needed.
Ondrej
2017-01-19 19:31 GMT+01:00 Santiago Pericasgeertsen <santiago.pericasgeertsen_at_oracle.com>:
On Jan 19, 2017, at 1:12 PM, Markus KARG <markus_at_headcrashing.eu> wrote:
Because in that "more complex" style you can write this then…
Client rxClient = client.register(Java8.class); // i. e. technically a CompletionStageRxInvokerProvider, which effectively is part of JAX-RS!!!
CompletionStage<UserPojo> cs =
rxClient.target("
http://foo.bar")
.request()
.rx(CompletionStage.class)
.get(UserPojo.class);
…so the average programmer clearly understands that this will register Java 8 as one possible RX provider (possibly in addition to others), so he can get a CompletionStage. It feels just more correct and simple, particular for JAX-RS beginners.
Of course, but the point raised by Pavel is that this code again cannot (at least not obviously) be accepted by the Java type checker. Hence, the less desirable suggestion. Please read the rest of the e-mail thread.
— Santiago
They will not understand why they shall repeat the provider again and again, and they will not understand why rx(T) will return not return T. But I think they will accept to repeat the stage again and again, as stages are not reusable (they are used to do that with stages already).
-Markus
From: Santiago Pericasgeertsen [mailto:santiago.pericasgeertsen_at_oracle.com] Sent: Donnerstag, 19. Januar 2017 16:55 To: jsr370-experts_at_jax-rs-spec.java.net Subject: Re: [jax-rs-spec users] JAX-RS 2.1 - work schedule
Hi Pavel,
Just catching up with this issue. I guess the two levels of indirection has led us into a generic wall :)
Client rxClient = client.register(CompletionStageRxInvokerProvider.class);
CompletionStage<UserPojo> cs =
rxClient.target("
http://foo.bar")
.request()
.rx(CompletionStage.class)
.get(UserPojo.class);
So what if we reduce indirection and write:
.rx(CompletionStageRxInvoker.class)
as before, still keeping the provider for it? Less ideal of course.
— Santiago