jsr339-experts@jax-rs-spec.java.net

[jsr339-experts] Re: Low-level JAX-RS client API proposal

From: Marek Potociar <marek.potociar_at_oracle.com>
Date: Fri, 13 May 2011 13:28:28 +0200

On 05/12/2011 08:28 PM, Markus KARG wrote:
>> Do you have in mind something like:
>>
>> @Path("somePath")
>> MyResource { ... }
>>
>> MyClient {
>> @ResourceRef(MyResource.class) WebResource resource; // URI
>> calculated automatically
>> ...
>> }
>>
>> ?
>
> Yes exactly that.

Ok, let me add that to the tasks - #106.

>>> * I'm not a big friend of abstract classes since it reduces the
>> freedom of the provider. Why not just providing an interface for
>> ClientFilter? I doubt that anybody is unable to write that single code
>> line that keeps the "next" reference. Also, as a WebResource is only to
>> be created by a builder and the builder is to be provided by a JAX-RS
>> implementation, I do not see a need to provide more than the
>> WebResource's interface as part of the spec. It should be completely up
>> to the implementation how WebResource works internally and what it
>> extends from.
>>
>> This is going to be redesigned as part of the task #99.
>>
>> As for the interface vs. abstract class, I prefer interfaces too, but
>> we need to understand that there is a serious
>> limitation: you cannot add new methods into an interface in a future
>> release without breaking BW compatibility.
>
> I think this could be solved, e. g. by versioned interfaces as Microsoft did in the Win32 API or by additional interfaces for new methods. We use the latter pattern a lot as having a single interface for a task often is typical but actually not needed.

The above works particularly well in designs where interface is representing some "able" or some specialization. When
you need to fix a missing method in the original interface, the results tend to be clumsy. Consider e.g. JAX-RS
core.Request - apparently the interface is missing many methods particularly from a filter/interceptor perspective. But
how to add more methods? By introducing e.g. MutableRequest? Sure, you could do it, but it still feels more like a
workaround (that we may want to apply eventually if we reach a consensus here - see task #76).

>
> Or we accept that breaking BW compatibility is not that big issue as we all might think: Nobody is forced to switch to the next generation of Java EE. If one does, he actually has much bigger problems that the need to recompile...

While I can personally see some benefits, that's not the common Java EE strategy. I would be very skeptical about
proposing to break BW compatibility.

>>> * I'm not a mathematical expert, but WebResourceBase's .hashCode()
>> implementation looks rather complex. I think "return
>> this.getClass().hashCode() ^ this.uri.hashCode()" would do and would be
>> much simpler? Looks like some black magic currently. ;-)
>>>
>>
>> Me neither, that's why I used my IDE to generate it for me. I would be
>> happy to change it if you can prove that your
>> version provides a widely spread hash codes :)
>
> What mass of WebResource instances do you plan to create at runtime that hashCode performance will be an issue actual? Do you even plan to use WebResource in any kind of Collection (otherwise nobody would call hashCode anyways)?

Not really, although one never knows. In fact I was really surprised that you focused at that part of the code. :)
>
> And: Can your IDE proof that your algorithm is better spread than mine? ;-)

It's a common pattern described and explained by Josh Bloch in his Effective Java book; see e.g. here (item 8):
http://java.sun.com/developer/Books/effectivejava/Chapter3.pdf

Just the constant numbers are different - 3/29 vs. 17/37 in the book. Still prime though and the book does not mandate
the use of those particular numbers.

As all I do is calling existing JVM hash code methods (which should be done well) and XORing them to not lose too much
information, I expect them to be rather well spread for the target use. :-)

Without a proof, I wouldn't expect that XORing two random ints would provide necessarily a good or better wide spread.
In fact, my feeling is that opposite behavior would be more probable :)

Anyway, as we both agree, this part of the code is not going to be the hot spot of the API, so why not to stick to
something that is commonly accepted as a good & correct approach?

>>> * Why insisting on ClientRequest being Cloneable?
>>
>> Since it seems we will have a fully mutable request, to be able to
>> reuse a preconfigured request in multiple
>> invocations, having it cloneable may come handy IMO.
>
> Can you give an example of ClientRequest reuse? Why not just using the same instance?

If the request is mutable, once you pass it to the framework you can never be sure that your instance you hold still
represents the same request as the one you used previously.

>>> * Is it essential to keep the ordering of cookies? If not, why then
>> returning List<NewCookie> instead of just Collection<NewCookie>?
>>
>> Is there any signinficant advantage behind that generalization? List
>> provides more manipulation methods compared to
>> collection. How about Set?
>
> You are reducing the freedom of the user of the Collection without any particular need.

How is that?

>
> It is a default pattern of mine to use the topmost generalization whenever possible, to give the user the most possible freedom.

To do what?

>
> Set would be better than List as it at least has a use: It expresses that a Cookie cannot be duplicate.
>
> But again, why *not* Collection?

I may want to change it to Set indeed. As for the generalization, I feel that it is better to:
- provide as specific interface as reasonably possible and to
- consume as general interface as possible

That's why I chose List... or if we agree that Set is better, then I would change it to Set. Maybe we can start a
separate thread on this particular thing? I'd like to hear what others have to say and I'm afraid not everyone is going
to follow these long email exchanges. Do you want to take action and start the new thread?

>
> What benefit does the caller have from List? Why should a caller manipulate the contents?

I'd say if the caller wanted to retrieve the content, the caller may well want to manipulate it somehow :)

>
>>> * Can you provide a use case when a user will apply the
>> ClientResponse.bufferEntity() method?
>>
>> E.g. you want to execute XQuery on the returned XML stream?
>
> Still didn't get the point (sorry I am using XPath a lot but am not used to XQuery). How will that work? If I will run XPath for example, I would just pick the entity, surround it with JAXBSource and run JAXP. No need for that method so far. So what is it actually good for?

Suppose you are on a very slow connection and don't want to process the input stream byte per byte. But I do not have
any particularly strong feelings about this method.

>>> * The current proposal seems to expect that a WebResources get
>> filtered ever or never, but it might be the case where filtering is
>> dependend of the current session. So the question is where we need to
>> support that. Currently it won't work as removing a filter while an
>> asynchrounous request is pending might lead to unknown behaviour.
>>
>> Can you share the use case in particular? I am confused by the notion
>> of the session - I thought we are shooting for a
>> stateless API.
>
> Yes and no. First, it was a hypothetical question. Second, the question is whether we need to support something like sessions (client-side, not server-side). See, the service's is stateless, but one might have a need to have some kind of transaction (not in the database sense), or let's better call it, a business process. That process involves lots of GET and PUT, which in the end create what the user wants (like a money transfer involves many GETs and PUTs on different resources). Each single invocation is stateless, but the process itself is definitively there and must keep state on the client (it must track the state of both accounts whilst the transfer is running and if one method fails it has to decide what to do with both accounts now). To model that, it might be beneficial to have sessions (aka business processes). Now it could happen that one such session needs a filter, while another does not. For example, if your credit card was stolen, the ATM (= client) shall tr
ack all communication with your account. Not with the bank server in generel, only with YOUR account. So the filter will be account dependent, not server or JAX-RS-Resource dependent.

Such state management concept belongs naturally to the layer above the proposed JAX-RS client api IMO. It would be
easier to decide if we need to add any support to the client API if we had more experience with particular use cases.
This may be a good candidate for a potential future enhancement.

>
>>> * I am the maintainer of the WebDAV package for JAX-RS and so I am
>> heavily interested in providing a WebDAVMethodInvoker as a replacement
>> for the HttpMethodInvoker. But I did not find a way how a calling
>> application could register interest is that. How to do that?
>>
>> So basically you want something like unwrap(Class<T> clazz) on a web
>> resource?
>
> I don't know if I want that ;-)
>
> The question more is: How does a client obtain an instance of HttpMethodInvoker to call one of the convenience methods? That mechanism shall be extendible, so a client can obtain an instance of WebDAVMethodInvoker instead to call .propfind() or .search() instead of .put() and .post().

Ok, I think I understand better now. Let me look at that.

>
>>> * A GUI might be heavily interested in getting updated after
>> receivering another HTTP chunk. I didn't find a way how an application
>> could register for such events. This is essential to show a progress
>> bar with long-lasting downloads.
>>
>> Let me have a look at that - added new subtask #105. But it seems to me
>> that this is something that should be done at
>> the transport layer.
>
> Yes but the problem is that the client has no influence on the transport layer. A client on Java SE cannot register at the transport level as it is up to the JAX RS Engine to select or implement the transport layer. Or did I miss something, like "Client.getTransport()" where a client could register for such events?

Seems to me that we may actually need some way to expose the transport (also based on the client cache discussion we are
having).

Marek

>
> Regards
> Markus
>