dev@jsr311.java.net

Re: Proposal for rework of spi package (was Re: "Dual dispatch")

From: Stefan Tilkov <stefan.tilkov_at_innoq.com>
Date: Wed, 25 Apr 2007 22:30:24 +0200

Hi Marc,

On Apr 25, 2007, at 10:04 PM, Marc Hadley wrote:

> On Apr 21, 2007, at 12:44 PM, Marc Hadley wrote:
>>
>>> I would favor the registry of externalizers discussed in another
>>> thread (registration with annotations), something like:
>>>
>>> @URITemplate(...)
>>> @Externalizer(Class<T>, Class<? extends
>>> TypeStreamingProvider<T>>) //example psuedo-signature
>>> public class InvoicesService {
>>> //..
>>> }
>>>
>> An annotation approach could also be good, in fact an annotation
>> could be used to generate the ServiceFinder configuration files
>> during apt processing.
>>
> Thinking about this some more I think we could rework the sketch2
> spi package quite a bit using these ideas (annotation based
> serializer generation and including media type as a key). As
> several people have commented I think this will make it less of an
> SPI so we should consider renaming the package or merging it into
> one of the other packages.
>
> Rather than configure TypeStreamingProviders in a config file as
> described in the ServiceFinder Javadoc we could instead use an
> annotation on the TypeStreamingProvider impl class. This would
> allow us to drop the ServiceFinder, ServiceConfiguration,
> TypeStreamingFactory and HeaderFactory classes from the API.
>
> Here's an example of how it could work, suppose you have a domain
> class Thing and you want to exchange serializations of it using
> media type "application/thing". You write a resource class that
> exposes the appropriate methods:
>
> @UriTemplate("/things/{thing_id}")
> public class ThingResource {
>
> public Thing(@UriParam("thing_id") String id) {...}
>
> @HttpMethod
> Thing get() {...}

I think this should still be

    @HttpMethod @ProduceMime("application/thing")
    Thing get() {...}

because in this class, I may want to return application/thing
content, and in another class (or even another method) I might want
to use a different content type.

> }
>
> and a TypeStreamingProvider for Thing
>
> @Externalizer(classes={Thing.class}, type="application/thing")
> public class ThingExternalizer implements
> TypeStreamingProvider<Thing.class> {
>
> boolean supports(Class<?> clazz, MediaType type) {
> return true; // always true since annotation above defines both
> class and type statically
> }
>
> Thing readFrom(Class<Thing> type, MediaType mediaType,
> MultivaluedMap<String, String> httpHeaders, InputStream
> entityStream) throws IOException {
> ...
> }
>
> void writeTo(Thing t, MediaType mediaType, MultivaluedMap<String,
> String> httpHeaders, OutputStream entityStream) throws IOException {
> ...
> }
> }
>
> An implementation can use an annotation processing tool to gather a
> list of TypeStreamingProviders during processing and generate
> whatever artifacts the impl needs to find the classes at runtime.
> The advantage is that the specification wouldn't need to tie down
> the method used as is currently the case with ServiceFinder.
>
> One downside I see is that this approach splits the metadata
> between two classes - you can't tell just by looking at the
> ThingResource class what media types are supported since that
> information is contained in the set of TypeStreamingProviders.

This problem would disappear with the suggestion above.

Other than that, this approach looks good (although I admit I'm still
not used to annotations enough to find it totally intuitive, but
that's probably my personal problem).

Best regards,
Stefan


> However it does separate the two concerns nicely so you could add
> support for an additional format just by dropping in a new
> TypeStreamingProvider.
>
> For more generic entity types like JAXB objects you could build a
> general purpose externalizer that would support any JAXB class like
> this:
>
> @Externalizer // no properties since set of classes and media types
> is open
> public class JAXBExternalizer implements
> TypeStreamingProvider<Object.class> {
> boolean supports(Class<?> clazz, MediaType type) {
> if (isJAXBClass(clazz)) // e.g. look for @XmlRootElement on class
> return true;
> return false;
> }
>
> Object readFrom(Class<Object> type, MediaType mediaType,
> MultivaluedMap<String, String> httpHeaders, InputStream
> entityStream) throws IOException {
> ...
> }
>
> void writeTo(Object t, MediaType mediaType,
> MultivaluedMap<String, String> httpHeaders, OutputStream
> entityStream) throws IOException {
> ...
> }
> }
>
> For truly generic types like String you can build an externalizer
> like this:
>
> @Externalizer(classes={String.class})
> public class JAXBExternalizer implements
> TypeStreamingProvider<String.class> {
> boolean supports(Class<?> clazz, MediaType type) {
> return true; // always true since can serialize any string
> regardless of media type
> }
>
> String readFrom(Class<String> type, MediaType mediaType,
> MultivaluedMap<String, String> httpHeaders, InputStream
> entityStream) throws IOException {
> ...
> }
>
> void writeTo(String t, MediaType mediaType,
> MultivaluedMap<String, String> httpHeaders, OutputStream
> entityStream) throws IOException {
> ...
> }
> }
>
> The specification would define a set of externalizers that an impl
> would be required to implement so an application only needs to add
> externalizers for custom types.
>
> Thoughts, comments ?
>
> Marc.
>
> ---
> Marc Hadley <marc.hadley at sun.com>
> CTO Office, Sun Microsystems.
>
>