You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@cxf.apache.org by Dawa Sherpa <da...@gmail.com> on 2008/11/17 23:10:19 UTC

Json and XML @ProduceMime

Is there a way I can produce both XML and JSON based on the same method.

In order for one to use _type=json|xml query param, there needs to be two
separate function mounted to a PATH.


How can I achieve alternate responses just based on one method ?


I tried doing
@ProduceMime("application/json, application/xml") , but still had no luck.

Dawa

Re: Json and XML @ProduceMime

Posted by Sergey Beryozkin <se...@progress.com>.
Hi Pedro

have a look please at

http://svn.apache.org/repos/asf/cxf/trunk/systests/src/test/resources/jaxrs/WEB-INF/beans.xml

it shows how you can override default consume or produce media types or both ones for a given provider from Spring, supported for 
JSON and JAXB ones for the moment.
Among other things this sample config also shows how you can associate, say, different version of JSONProvider (or indeed any other 
provider) with different endpoint addresses which might represent the same bean...

Thanks, Sergey

>
> Hi Sergey,
>
> Thanks for the answer. I think I can explain better my issue. I wrote it
> very quickly at work, looking forward to go home, and now I've seen some
> mistakes in my explanation.
>
> But I think it easier if you see this way:
>
> I was doing a REST API with XML replies. So I use this annotations in my
> bean:
>
> @Produces("text/xml")
> @Consumes("text/xml")
> class MyService {.....}
>
> But you can really reply whatever content you want. That annotations are
> Content-Type headers issues. You even might reply binary data (sure, client
> won't understand, but your bean will do the work you want).
>
> Then I decided to use JSON replies, with Jettison (Implemented in
> JSONProvider). And to be robust, I did this:
>
> @ProduceMime({"text/json", "json/application"})
> @ConsumeMime({"text/json", "json/application"})
> class MyService {.....}
>
> There is still lot of confusion about right MIME type for JSON. So my bean
> can support both. But then I saw that it didn't work.
>
> With "Accept: text/json" requests, the responses were still XML, and with
> "json/aplication" were JSON. Why?. When I don't use JSONProvier and if I
> reply an String with JSON it works with any content-type.
>
> Then I copied JSONProvider class and I changed annotations:
>
> @ProduceMime("*/*")
> @CosumeMime("*/*")
> Same as if you delete them.
>
> And it works, my beans uses its annotations, not JSONProvider annotations.
> The annotations I configured.
>
> Seeing the problem I was looking a solution without library code
> modifications.
>
> One solution is to subclass JSONProvider, just to change the annotations,
> but I couldn't subclass it, about the final (I suppose the final is right
> here, it is only that I couldn't do what I wanted, "final" isn't the
> problem).
>
> Then the other solution is delegation, and it works, perfectly, without
> library modifications, but requires more code.
>
> I hope this explain my point of view.
>
> Thanks,
>   Pedro
>
>
>
> Hi Pedro
>
>> But when you use JSONProvider you have to use "application/json" to
> get
> "JSON".
>
> Yes, it's hardcoded at the moment
>
>> I know it doesn't right not to use "application/json", but I think
> that
> should be developer decision.
>
> Actually, I agree with you - whatever fits best the developer's
> application is the right decision.
>
>> public final class JSONProvider extends AbstractJAXBProvider  { ... }
>
> I'll fix it - I see no reason in it being final - I didn't even know it
> was :-)
>
>>@ProduceMime("text/plain" )
>>public ContactEntry  get() { ... }
>
>>Produces XML because I'm using JAXB. (It isn't good using XML like
> plain
>
> Actually, I updated the JAXB provider to produce/consume either
> application/xml or text/xml
>
>>     <bean id="jsonProvider"
> class="org.apache.cxf.jaxrs.provider.JSONProvider"/>
>
> json is supported by default so there's no need to explicitly register
> it unless you need to configure a JSON provider or pass it to a
> delegation handler as in your case.
>
>
> Overall you raise an interesting question : how to extend providers to
> support arbitrary mime types. I've seen this issue raised at the Jersey
> list too.
>
> I reckon we can address this issue similarly to the way you suggested.
> We can start with JAXB and JSON ones and update other providers as
> needed.
> Basically, they can omit static Produces/Consumes definitions (which is
> equivalent to using */* as their values) and have a list of mime types
> supported by default instead.
>
> So for ex, in case of JSON we'll have
>
> @Provider
> public class JSONProvider extends AbstractJAXBProvider  {
>
> protected List<MediaType> consumeTypes = ...
> protected List<MediaType> produceTypes = ...
> // will contain application/json at init
> }
>
> And have the setter method which will allow either append to or
> overwrite this list from Spring.
>
> Next, in isWriteable, isReadable we will match these values against the
> Produces/Consumes on Class/Method
>
> I need to think about it a bit more...
>
> Thanks, Sergey
>
> -- 
> View this message in context: http://www.nabble.com/Json-and-XML-%40ProduceMime-tp20549326p20741653.html
> Sent from the cxf-user mailing list archive at Nabble.com.
>
> 



RE: Json and XML @ProduceMime

Posted by Pedro Ballesteros <pm...@tid.es>.
Hi Sergey,

Thanks for the answer. I think I can explain better my issue. I wrote it
very quickly at work, looking forward to go home, and now I've seen some
mistakes in my explanation.

But I think it easier if you see this way:

I was doing a REST API with XML replies. So I use this annotations in my
bean:

@Produces("text/xml")
@Consumes("text/xml")
class MyService {.....}

But you can really reply whatever content you want. That annotations are
Content-Type headers issues. You even might reply binary data (sure, client
won't understand, but your bean will do the work you want).

Then I decided to use JSON replies, with Jettison (Implemented in
JSONProvider). And to be robust, I did this:

@ProduceMime({"text/json", "json/application"})
@ConsumeMime({"text/json", "json/application"})
class MyService {.....}

There is still lot of confusion about right MIME type for JSON. So my bean
can support both. But then I saw that it didn't work.

With "Accept: text/json" requests, the responses were still XML, and with
"json/aplication" were JSON. Why?. When I don't use JSONProvier and if I
reply an String with JSON it works with any content-type. 

Then I copied JSONProvider class and I changed annotations:

@ProduceMime("*/*")
@CosumeMime("*/*")
Same as if you delete them.

And it works, my beans uses its annotations, not JSONProvider annotations.
The annotations I configured. 

Seeing the problem I was looking a solution without library code
modifications. 

One solution is to subclass JSONProvider, just to change the annotations,
but I couldn't subclass it, about the final (I suppose the final is right
here, it is only that I couldn't do what I wanted, "final" isn't the
problem). 

Then the other solution is delegation, and it works, perfectly, without
library modifications, but requires more code.

I hope this explain my point of view.

Thanks,
   Pedro



Hi Pedro

> But when you use JSONProvider you have to use "application/json" to
get
"JSON".

Yes, it's hardcoded at the moment

> I know it doesn't right not to use "application/json", but I think
that
should be developer decision.

Actually, I agree with you - whatever fits best the developer's
application is the right decision. 

> public final class JSONProvider extends AbstractJAXBProvider  { ... }

I'll fix it - I see no reason in it being final - I didn't even know it
was :-)

>@ProduceMime("text/plain" )
>public ContactEntry  get() { ... }

>Produces XML because I'm using JAXB. (It isn't good using XML like
plain

Actually, I updated the JAXB provider to produce/consume either
application/xml or text/xml

>     <bean id="jsonProvider"
class="org.apache.cxf.jaxrs.provider.JSONProvider"/>

json is supported by default so there's no need to explicitly register
it unless you need to configure a JSON provider or pass it to a
delegation handler as in your case.


Overall you raise an interesting question : how to extend providers to
support arbitrary mime types. I've seen this issue raised at the Jersey
list too.

I reckon we can address this issue similarly to the way you suggested.
We can start with JAXB and JSON ones and update other providers as
needed.
Basically, they can omit static Produces/Consumes definitions (which is
equivalent to using */* as their values) and have a list of mime types
supported by default instead.

So for ex, in case of JSON we'll have

@Provider
public class JSONProvider extends AbstractJAXBProvider  {

protected List<MediaType> consumeTypes = ...
protected List<MediaType> produceTypes = ...
// will contain application/json at init
}

And have the setter method which will allow either append to or
overwrite this list from Spring.

Next, in isWriteable, isReadable we will match these values against the
Produces/Consumes on Class/Method

I need to think about it a bit more...

Thanks, Sergey

-- 
View this message in context: http://www.nabble.com/Json-and-XML-%40ProduceMime-tp20549326p20741653.html
Sent from the cxf-user mailing list archive at Nabble.com.


RE: Json and XML @ProduceMime

Posted by Sergey Beryozkin <sb...@progress.com>.
Hi Pedro

> But when you use JSONProvider you have to use "application/json" to
get
"JSON".

Yes, it's hardcoded at the moment

> I know it doesn't right not to use "application/json", but I think
that
should be developer decision.

Actually, I agree with you - whatever fits best the developer's
application is the right decision. 

> public final class JSONProvider extends AbstractJAXBProvider  { ... }

I'll fix it - I see no reason in it being final - I didn't even know it
was :-)

>@ProduceMime("text/plain" )
>public ContactEntry  get() { ... }

>Produces XML because I'm using JAXB. (It isn't good using XML like
plain

Actually, I updated the JAXB provider to produce/consume either
application/xml or text/xml

>     <bean id="jsonProvider"
class="org.apache.cxf.jaxrs.provider.JSONProvider"/>

json is supported by default so there's no need to explicitly register
it unless you need to configure a JSON provider or pass it to a
delegation handler as in your case.


Overall you raise an interesting question : how to extend providers to
support arbitrary mime types. I've seen this issue raised at the Jersey
list too.

I reckon we can address this issue similarly to the way you suggested.
We can start with JAXB and JSON ones and update other providers as
needed.
Basically, they can omit static Produces/Consumes definitions (which is
equivalent to using */* as their values) and have a list of mime types
supported by default instead.

So for ex, in case of JSON we'll have

@Provider
public class JSONProvider extends AbstractJAXBProvider  {

protected List<MediaType> consumeTypes = ...
protected List<MediaType> produceTypes = ...
// will contain application/json at init
}

And have the setter method which will allow either append to or
overwrite this list from Spring.

Next, in isWriteable, isReadable we will match these values against the
Produces/Consumes on Class/Method

I need to think about it a bit more...

Thanks, Sergey



-----Original Message-----
From: Pedro Ballesteros [mailto:pmbh@tid.es] 
Sent: 28 November 2008 19:30
To: users@cxf.apache.org
Subject: Re: Json and XML @ProduceMime


But I think there is a problem with JSONProvider. Because when you don't
use
it, you can configure whatever Content-type you like in your service
class,
and your body-content could be a different content-type.

But when you use JSONProvider you have to use "application/json" to get
"JSON". If you use another Content-Type, like "text/json" or whatever
you
want,  JSONProvider doesn't receive the request and you get XML, not
JSON.

The problem is @ProduceMime and @ConsumeMime in JSONProvider, that you
can't
override with inheritance because it is final class. So, I had to use
Delegation Pattern. 

I tell details here:

When you use JSONProvider your services only can work with MIME 
"application/json". If you configure another MIME in your service,
JSONProvicer doesn't process the request and doesn't change XML into
JSON.

I know it doesn't right not to use "application/json", but I think that
should be developer decision. Maybe I need to use "plain/text",
"json/text",
even if it's wrong.

The problem is just JSONProvider Produces, Consumes declaration:

@Produces("application/json")
@Consumes("application/json")
@Provider
public final class JSONProvider extends AbstractJAXBProvider  { ... }

And you can't override class Annotations because JSONProvider is final.

I think it should be this way:

@Produces("*/*")
@Consumes("*/*")
@Provider
public final class JSONProvider extends AbstractJAXBProvider  { ... }

So that developers can choose Produces and Consumers they mean in
Service
Beans.

Ej:
When I don't use JSONProvider the behavior is like this:

@GET
@ProduceMime("text/plain" )
public ContactEntry  get() { ... }

Produces XML because I'm using JAXB. (It isn't good using XML like plain
text content type, I know, but I can).

@GET
@ProduceMime("text/xml" )
public ContactEntry  get() { ... }

Produces XML too. (That's better).

So I can choose the content type when I don't use JSONProvider, even if
i'm
doing bad things.

But if I use JSONProvider:

<jaxrs:server id="contactsRemoteService" address="/">
        <jaxrs:serviceBeans>
            <ref bean="contactsService" />
        </jaxrs:serviceBeans>

        <jaxrs:providers>
            <ref bean="jsonProvider" />
        </jaxrs:providers>

    </jaxrs:server>

    <bean id="jsonProvider"
class="org.apache.cxf.jaxrs.provider.JSONProvider"/>

With: 

@GET
@ProduceMime("text/plain" )
public ContactEntry  get() { ... }

or

@GET
@ProduceMime("text/json" )
public ContactEntry  get() { ... }

My service produces XML, but with:

@GET
@ProduceMime("json/application" )
public ContactEntry  get() { ... }

It produces JSON.

Maybe I'd like to configure  my service like that, I can't. And as
JSONProvider is a final class, the only solution is delegation pattern:

This code is tested:

// JSON don't decides Content-Type
@ProduceMime("*/*")
@ConsumeMime("*/*")
@Provider
public class FreeMimeJSONProvider extends AbstractJAXBProvider {
    private JSONProvider jsonProvider;

    public void setJsonProvider(JSONProvider jsonProvider) {
        this.jsonProvider = jsonProvider;
    }
      
    @Override
    public void setSchemas(List<String> locations) {
        jsonProvider.setSchemas(locations);
    }
    
    public void setNamespaceMap(Map<String, String> namespaceMap) {
        jsonProvider.setNamespaceMap(namespaceMap);
    }

    public Object readFrom(Class type, Type genericType, Annotation[]
annotations, MediaType m, 
        MultivaluedMap<String, String> headers, InputStream is) 
        throws IOException {

        return jsonProvider.readFrom(type, genericType, annotations, m,
headers, is);
    }

    public void writeTo(Object obj, Class<?> cls, Type genericType,
Annotation[] anns,  
        MediaType m, MultivaluedMap<String, Object> headers,
OutputStream
os)
        throws IOException {

        jsonProvider.writeTo(obj, cls, genericType, anns, m, headers,
os);
    }
}

So Content-Types are configured in service classes, as always:

@Path ("/")
public class ContactsService  {
    @GET
    @ProduceMime("text/json" ) // I'm using JSONProvider but I wan't
"text/json" MIME
    public ContactEntry  get() {
                 ....
    }
}

And in Spring:

 <jaxrs:server id="contactsRemoteService" address="/">
        <jaxrs:serviceBeans>
            <ref bean="contactsService" />
        </jaxrs:serviceBeans>
        <jaxrs:providers>
            <ref bean="jsonProvider" />
        </jaxrs:providers>
    </jaxrs:server>

    <!-- JSONProvider inside My Provider just to override Consumers
Produces
Annotations -->
    <bean id="jsonProvider" class="aa.aaaaa.aaaaa.FreeMimeJSONProvider">
        <property name="jsonProvider">
            <bean class="org.apache.cxf.jaxrs.provider.JSONProvider"/>
        </property> 
    </bean>
  
   <bean id="contactsService" class="aa.aaaaa.aaaaa.ContactsService" /> 


Best Regards,

    Pedro




Sergey Beryozkin-3 wrote:
> 
> Hi Dawa
> 
> No problems at all - good news it's all working for you now...
> 
> Cheers, Sergey
> 
>> Sorry for polluting the Mailing list. I figured out why my problem
was.
>> 
>> I had to use
>> 
>> @ProduceMime({"application/json", "application/xml"})
>> 
>> Now, I am happily switching the response type based on the query
param
>> _type.
>> 
>> Dawa :-D
>> 
>> On Mon, Nov 17, 2008 at 2:10 PM, Dawa Sherpa <da...@gmail.com>
wrote:
>> 
>>> Is there a way I can produce both XML and JSON based on the same
method.
>>>
>>> In order for one to use _type=json|xml query param, there needs to
be
>>> two
>>> separate function mounted to a PATH.
>>>
>>>
>>> How can I achieve alternate responses just based on one method ?
>>>
>>>
>>> I tried doing
>>> @ProduceMime("application/json, application/xml") , but still had no
>>> luck.
>>>
>>> Dawa
>>>
>>
> 
> 
> 

-- 
View this message in context:
http://www.nabble.com/Json-and-XML-%40ProduceMime-tp20549326p20739465.ht
ml
Sent from the cxf-user mailing list archive at Nabble.com.


Re: Json and XML @ProduceMime

Posted by Pedro Ballesteros <pm...@tid.es>.
But I think there is a problem with JSONProvider. Because when you don't use
it, you can configure whatever Content-type you like in your service class,
and your body-content could be a different content-type.

But when you use JSONProvider you have to use "application/json" to get
"JSON". If you use another Content-Type, like "text/json" or whatever you
want,  JSONProvider doesn't receive the request and you get XML, not JSON.

The problem is @ProduceMime and @ConsumeMime in JSONProvider, that you can't
override with inheritance because it is final class. So, I had to use
Delegation Pattern. 

I tell details here:

When you use JSONProvider your services only can work with MIME 
"application/json". If you configure another MIME in your service,
JSONProvicer doesn't process the request and doesn't change XML into JSON.

I know it doesn't right not to use "application/json", but I think that
should be developer decision. Maybe I need to use "plain/text", "json/text",
even if it's wrong.

The problem is just JSONProvider Produces, Consumes declaration:

@Produces("application/json")
@Consumes("application/json")
@Provider
public final class JSONProvider extends AbstractJAXBProvider  { ... }

And you can't override class Annotations because JSONProvider is final.

I think it should be this way:

@Produces("*/*")
@Consumes("*/*")
@Provider
public final class JSONProvider extends AbstractJAXBProvider  { ... }

So that developers can choose Produces and Consumers they mean in Service
Beans.

Ej:
When I don't use JSONProvider the behavior is like this:

@GET
@ProduceMime("text/plain" )
public ContactEntry  get() { ... }

Produces XML because I'm using JAXB. (It isn't good using XML like plain
text content type, I know, but I can).

@GET
@ProduceMime("text/xml" )
public ContactEntry  get() { ... }

Produces XML too. (That's better).

So I can choose the content type when I don't use JSONProvider, even if i'm
doing bad things.

But if I use JSONProvider:

<jaxrs:server id="contactsRemoteService" address="/">
        <jaxrs:serviceBeans>
            <ref bean="contactsService" />
        </jaxrs:serviceBeans>

        <jaxrs:providers>
            <ref bean="jsonProvider" />
        </jaxrs:providers>

    </jaxrs:server>

    <bean id="jsonProvider"
class="org.apache.cxf.jaxrs.provider.JSONProvider"/>

With: 

@GET
@ProduceMime("text/plain" )
public ContactEntry  get() { ... }

or

@GET
@ProduceMime("text/json" )
public ContactEntry  get() { ... }

My service produces XML, but with:

@GET
@ProduceMime("json/application" )
public ContactEntry  get() { ... }

It produces JSON.

Maybe I'd like to configure  my service like that, I can't. And as
JSONProvider is a final class, the only solution is delegation pattern:

This code is tested:

// JSON don't decides Content-Type
@ProduceMime("*/*")
@ConsumeMime("*/*")
@Provider
public class FreeMimeJSONProvider extends AbstractJAXBProvider {
    private JSONProvider jsonProvider;

    public void setJsonProvider(JSONProvider jsonProvider) {
        this.jsonProvider = jsonProvider;
    }
      
    @Override
    public void setSchemas(List<String> locations) {
        jsonProvider.setSchemas(locations);
    }
    
    public void setNamespaceMap(Map<String, String> namespaceMap) {
        jsonProvider.setNamespaceMap(namespaceMap);
    }

    public Object readFrom(Class type, Type genericType, Annotation[]
annotations, MediaType m, 
        MultivaluedMap<String, String> headers, InputStream is) 
        throws IOException {

        return jsonProvider.readFrom(type, genericType, annotations, m,
headers, is);
    }

    public void writeTo(Object obj, Class<?> cls, Type genericType,
Annotation[] anns,  
        MediaType m, MultivaluedMap<String, Object> headers, OutputStream
os)
        throws IOException {

        jsonProvider.writeTo(obj, cls, genericType, anns, m, headers, os);
    }
}

So Content-Types are configured in service classes, as always:

@Path ("/")
public class ContactsService  {
    @GET
    @ProduceMime("text/json" ) // I'm using JSONProvider but I wan't
"text/json" MIME
    public ContactEntry  get() {
                 ....
    }
}

And in Spring:

 <jaxrs:server id="contactsRemoteService" address="/">
        <jaxrs:serviceBeans>
            <ref bean="contactsService" />
        </jaxrs:serviceBeans>
        <jaxrs:providers>
            <ref bean="jsonProvider" />
        </jaxrs:providers>
    </jaxrs:server>

    <!-- JSONProvider inside My Provider just to override Consumers Produces
Annotations -->
    <bean id="jsonProvider" class="aa.aaaaa.aaaaa.FreeMimeJSONProvider">
        <property name="jsonProvider">
            <bean class="org.apache.cxf.jaxrs.provider.JSONProvider"/>
        </property> 
    </bean>
  
   <bean id="contactsService" class="aa.aaaaa.aaaaa.ContactsService" /> 


Best Regards,

    Pedro




Sergey Beryozkin-3 wrote:
> 
> Hi Dawa
> 
> No problems at all - good news it's all working for you now...
> 
> Cheers, Sergey
> 
>> Sorry for polluting the Mailing list. I figured out why my problem was.
>> 
>> I had to use
>> 
>> @ProduceMime({"application/json", "application/xml"})
>> 
>> Now, I am happily switching the response type based on the query param
>> _type.
>> 
>> Dawa :-D
>> 
>> On Mon, Nov 17, 2008 at 2:10 PM, Dawa Sherpa <da...@gmail.com> wrote:
>> 
>>> Is there a way I can produce both XML and JSON based on the same method.
>>>
>>> In order for one to use _type=json|xml query param, there needs to be
>>> two
>>> separate function mounted to a PATH.
>>>
>>>
>>> How can I achieve alternate responses just based on one method ?
>>>
>>>
>>> I tried doing
>>> @ProduceMime("application/json, application/xml") , but still had no
>>> luck.
>>>
>>> Dawa
>>>
>>
> 
> 
> 

-- 
View this message in context: http://www.nabble.com/Json-and-XML-%40ProduceMime-tp20549326p20739465.html
Sent from the cxf-user mailing list archive at Nabble.com.


Re: Json and XML @ProduceMime

Posted by Sergey Beryozkin <se...@progress.com>.
Hi Dawa

No problems at all - good news it's all working for you now...

Cheers, Sergey

> Sorry for polluting the Mailing list. I figured out why my problem was.
> 
> I had to use
> 
> @ProduceMime({"application/json", "application/xml"})
> 
> Now, I am happily switching the response type based on the query param
> _type.
> 
> Dawa :-D
> 
> On Mon, Nov 17, 2008 at 2:10 PM, Dawa Sherpa <da...@gmail.com> wrote:
> 
>> Is there a way I can produce both XML and JSON based on the same method.
>>
>> In order for one to use _type=json|xml query param, there needs to be two
>> separate function mounted to a PATH.
>>
>>
>> How can I achieve alternate responses just based on one method ?
>>
>>
>> I tried doing
>> @ProduceMime("application/json, application/xml") , but still had no luck.
>>
>> Dawa
>>
>


Re: Json and XML @ProduceMime

Posted by Dawa Sherpa <da...@gmail.com>.
Sorry for polluting the Mailing list. I figured out why my problem was.

I had to use

@ProduceMime({"application/json", "application/xml"})

Now, I am happily switching the response type based on the query param
_type.

Dawa :-D

On Mon, Nov 17, 2008 at 2:10 PM, Dawa Sherpa <da...@gmail.com> wrote:

> Is there a way I can produce both XML and JSON based on the same method.
>
> In order for one to use _type=json|xml query param, there needs to be two
> separate function mounted to a PATH.
>
>
> How can I achieve alternate responses just based on one method ?
>
>
> I tried doing
> @ProduceMime("application/json, application/xml") , but still had no luck.
>
> Dawa
>