users@jersey.java.net

Re: [Jersey] JSONP Outputfilter

From: Paul Sandoz <Paul.Sandoz_at_Sun.COM>
Date: Mon, 27 Jul 2009 10:31:39 +0200

Hi,

There was something in your initial email that i did not pick up, you
state:

   I'm trying to implement JSONP on top of a solution that allready
produces JSON and XML

Note that the use of JSONWithPadding supports JSON or any other media
type:

   https://jersey.dev.java.net/nonav/apidocs/1.1.1-ea/jersey/com/sun/jersey/api/json/JSONWithPadding.html

The advantage of a filter is that a developer does not have to
explicitly wrap the response entity. But there is also a disadvantage
of a filter, in that it can hide what is going on, especially if it is
container-wide. Another solution is to utilize a resource method
filter rather than an container filter. The resource method filter can
look at the @Produces, or be triggered via an annotation that you
define (optionally the callback function name can be declared in the
annotation).


On Jul 24, 2009, at 4:00 PM, tarjei wrote:

> Hi, thanks to Pauls inputs, I got the following filter to work:
>
> More readable url: http://pastebin.ca/1505767
>
> All I am wondering about now is a) If this filter should be part of
> some contrib package)

It seems worthwhile to have container and resource method filters. Do
you want to contribute a patch?


> and b) if there is a better check than instanceof
> java.util.Collection that should be used. I tried to find the check
> that is used in the source, but I didn't find it.
>
> public class JSONCallbackResponseFilter implements
> ContainerResponseFilter {
> public ContainerResponse filter(ContainerRequest request,
> ContainerResponse response) {
>
> MultivaluedMap<String, Object> headers = response.getHttpHeaders();
> MediaType contentType = this.getContentType(headers);
> /* no entity, no jsonp response */
> if (response.getEntity() == null || contentType == null ||
> false == contentType.getSubtype().equals("x-javascript") ||
> false == contentType.getType().equals("application")) {
> return response;
> }

The same set of JavaScript media types should be supported as that for
JSONWithPadding. One way to do this is to have a public static method
on JSONWithPadding.


> final Object entity =response.getEntity();
> //if ( entity instanceof java.util.Collection ) { // reconstruct
> genereic entity
> if (GenericEntity.class.isAssignableFrom(entity.getClass())) {
> response.setEntity(
> new JSONWithPadding(
> new GenericEntity(entity, response.getEntityType())));
> } else {
> response.setEntity(new JSONWithPadding(entity));
> }


The above is not correct, you need to do something like the following:

   if (response.getEntity() instanceof JSONWithPadding) {
     if (response.getEntity().getClass() == response.getEntityType()) {
       response.setEntity(new JSONWithPadding(response.getEntity());
     } else {
       response.setEntity(new JSONWithPadding(
           new GenericEntity(response.getEntity(),
response.getEntityType())));
     }
   }

Paul.

> return response;
> }
>
> private MediaType getContentType(MultivaluedMap<String, Object>
> headers) {
> final Object mediaTypeHeader = headers.getFirst("Content-
> Type");
> if (mediaTypeHeader instanceof MediaType) {
> return (MediaType) mediaTypeHeader;
> } else if (mediaTypeHeader != null) {
> return MediaType.valueOf(mediaTypeHeader.toString());
> }
> return null;
> }
>
> }
>
> Regards,
> Tarjei
>
>
>
>
> On 07/24/2009 02:42 PM, Paul Sandoz wrote:
>>
>> On Jul 24, 2009, at 2:05 PM, tarjei wrote:
>>>
>>>>
>>>>
>>>> It is clear from looking at the ContainerResponse class that
>>>> there are a
>>>> number of ways this can be made easier for you, such as
>>>> ContainerResponse.getContentType can be made public.
>>> So it is not possible to get the contenttype for the response atm?
>>>
>>
>> Yes, it is. You need to copy the code from ContainerResponse:
>>
>> private MediaType getContentType() {
>> final Object mediaTypeHeader = getHttpHeaders().getFirst("Content-
>> Type");
>> if (mediaTypeHeader instanceof MediaType) {
>> return (MediaType)mediaTypeHeader;
>> } else if (mediaTypeHeader != null) {
>> return MediaType.valueOf(mediaTypeHeader.toString());
>> }
>> return null;
>> }
>>
>>
>>
>>> I change the code to this:
>>> if ( (!
>>> request.getAcceptableMediaTypes().contains("application/x-
>>> javascript"))
>>
>> That will not work because you need to use an instance of MediaType.
>>
>>
>>> ||
>>> // some other type is preferable to the client
>>> !
>>> request.getAcceptableMediaTypes().get(0).equals("application/x-
>>> javascript")
>>> ) {
>>> return response;
>>> }
>>>
>>> So to at least reduce the problem.
>>>
>>> Also I check for GenericEntity:
>>> if (response.getEntity() != null && ! (response.getEntity()
>>> instanceof
>>> GenericType)) {
>>> response.setEntity(new JSONWithPadding(response.getEntity()));
>>> return response;
>>> }
>>>
>>> But that didn't work.
>>>
>>
>> ContainerResponse.setEntity works as follows:
>>
>> public void setEntity(Object entity) {
>> this.entity = entity;
>> if (this.entity instanceof GenericEntity) {
>> final GenericEntity ge = (GenericEntity)this.entity;
>> this.entityType = ge.getType();
>> this.entity = ge.getEntity();
>> } else {
>> if (entity != null)
>> this.entityType = this.entity.getClass();
>> }
>> checkStatusAndEntity();
>> }
>>
>> It deconstructs a GenericEntity into its parts.
>>
>>
>>
>>>> It should be possible to wrap the entity around a JSONWithPadding
>>>> instance and then you do not need to adapt the
>>>> ContainerResponseWriter.
>>>> The following can be done for cases when GenericEntity is not used
>>>> (which is created when stuff like List<T> is used). So you can do:
>>>
>>> Why doesn't it work with GenericEntity?
>>
>> See above.
>>
>>
>>> What should I do in those situations?
>>>
>>
>> You can construct the GenericEntity from getEntity and
>> getEntityType, so
>> if the latter is not null.
>>
>>
>>> I tried this:
>>> if (response.getEntity() instanceof GenericEntity) {
>>> GenericEntity entity = (GenericEntity<?>) response.getEntity();
>>> response.setEntity(new JSONWithPadding(entity.getEntity()));
>>> return response;
>>> }
>>>
>>> But that didn't work as the entity I get from the method is an
>>> ArrayList, i.e. not an GenericEntity yet.
>>>
>>> The filter as it stands now looks like this:
>>>
>>> public ContainerResponse filter(ContainerRequest request,
>>> ContainerResponse response) {
>>>
>>> if ( (! request.getAcceptableMediaTypes().contains(new
>>> MediaType("application","x-javascript")))
>>
>>> ||
>>> // some other type is preferable
>>> ! request.getAcceptableMediaTypes().get(0).equals(new
>>> MediaType("application","x-javascript"))
>>> ) {
>>> log.info("Request does not contain application/x-javascript
>>> mediatype.
>>> returning normal requset.");
>>> return response;
>>> }
>>
>> You need to change the above to check the content type. Change the
>> above to:
>>
>> MediaType ct = getContentType();
>> if (ct == null || !ct.getSubType().equals("x-javascript") ||
>> !ct.getType().equals("application"))) {
>> return;
>> }
>>
>>
>>
>>
>>> log.info("Response: " + response.getEntity().getClass().getName());
>>> if (response.getEntity() != null && ! (response.getEntity()
>>> instanceof
>>> GenericEntity)) {
>>>
>>> response.setEntity(new JSONWithPadding(response.getEntity()));
>>> return response;
>>> }
>>>
>>> if (response.getEntity() instanceof GenericEntity) {
>>> GenericEntity entity = (GenericEntity<?>) response.getEntity();
>>> response.setEntity(new JSONWithPadding(entity.getRawType()));
>>> log.info("Returning generic type");
>>> return response;
>>> }
>>
>>
>> Change the above to:
>>
>> if (response.getEntity() != null) {
>> Object entity = response.getEntity();
>> if (response.getEntuityType() != null) {
>> entity = new GenericEntity(entity, response.getEntuityType());
>> }
>>
>> response.setEntity(new JSONWithPadding(entity));
>> }
>>
>>
>>> log.info("No entity returning response as is.");
>>> return response;
>>>
>>> }
>>>
>>>
>>> PS: MediaType.equals should return true strings representing
>>> mediatypes like this:
>>> type.equals("application/x-javascript")
>>>
>>
>> Currently it only works on instances of MediaType, which is what the
>> contract and equals specifies in the JavaDoc.
>>
>> Paul.
>>
>>> kind regards,
>>> Tarjei
>>>
>>>> if (content type is "application/x-javascript" &&
>>>> response.getEntity()
>>>> != null) {
>>>> response.setEntity(new JSONWithPadding(response.getEntity());
>>>> }
>>>>
>>>>
>>>>
>>>> So i think you can get things to work, but in summary there also
>>>> need to
>>>> be some improvements to make this easier:
>>>>
>>>> 1) ContainerResponseWriter.finish needs to be called when there
>>>> is no
>>>> response entity.
>>>>
>>>> 2) ContainerResponse.getContentType should be a public method.
>>>>
>>>> 3) It should be possible to get the "raw" entity that was
>>>> returned by a
>>>> resource method.
>>>>
>>>> Paul.
>>>>
>>>> On Jul 24, 2009, at 11:47 AM, tarjei wrote:
>>>>
>>>>> Hi, I'm trying to implement JSONP on top of a solution that
>>>>> allready
>>>>> produces JSON and XML. As such, I do not want to rewrite my
>>>>> classes to
>>>>> return JSONWithPadding objects.
>>>>>
>>>>> I found some information [1] saying that I can do this by
>>>>> implementing
>>>>> an extra filter that adds wraps the JSON with a callback.
>>>>>
>>>>> I've tried implementing the filter, but somehow I'm missing an
>>>>> important line as the filter only returns callback=( and no
>>>>> content
>>>>> when I try to use it. The filter is below. I think I'm missing
>>>>> just
>>>>> something crucial, I just do not know what :)
>>>>>
>>>>> Is there a transparent way to do this in Jersey that I do not know
>>>>> about?
>>>>>
>>>>>
>>>>> /**
>>>>> * See: http://n2.nabble.com/JSONP-Callback-support-tc2260544.html
>>>>> */
>>>>> public class JSONCallbackResponseFilter implements
>>>>> ContainerResponseFilter {
>>>>> Logger log = Logger.getLogger(getClass());
>>>>> public ContainerResponse filter(ContainerRequest request,
>>>>> ContainerResponse response) {
>>>>> if (!request.getAcceptableMediaTypes()
>>>>> .contains("application/x-javascript")) {
>>>>> log.info("Request does not contain ok mediatype. returning normal
>>>>> info.");
>>>>> return response;
>>>>> }
>>>>>
>>>>> MultivaluedMap<String, String> queryParamsMapMulti =
>>>>> request.getQueryParameters();
>>>>> String callback = queryParamsMapMulti.getFirst("callback");
>>>>> log.debug("callback=" + callback);
>>>>> if (callback != null) {
>>>>> response.setContainerResponseWriter(
>>>>> new JSONCallbackResponseAdapter(
>>>>> response.getContainerResponseWriter(),
>>>>> callback));
>>>>> }
>>>>> return response;
>>>>> }
>>>>>
>>>>> /**
>>>>> * * * See:
>>>>> *
>>>>> http://n2.nabble.com/-Filter--how-to-customize-context-body---td2116754.html
>>>>>
>>>>> */
>>>>> public static final class JSONCallbackResponseAdapter implements
>>>>> ContainerResponseWriter {
>>>>> private final ContainerResponseWriter crw;
>>>>>
>>>>> private OutputStream out;
>>>>> private String callback;
>>>>>
>>>>> JSONCallbackResponseAdapter(ContainerResponseWriter crw, String
>>>>> callback) {
>>>>> this.crw = crw;
>>>>> this.callback = callback;
>>>>> }
>>>>>
>>>>> public OutputStream writeStatusAndHeaders(
>>>>> long contentLength,
>>>>> ContainerResponse response) throws IOException {
>>>>> out = crw.writeStatusAndHeaders(-1, response);
>>>>>
>>>>> out.write((this.callback + "(").getBytes());
>>>>> return out;
>>>>> }
>>>>>
>>>>> public void finish() throws IOException {
>>>>> out.write(")".getBytes());
>>>>> }
>>>>> }
>>>>> }
>>>>>
>>>>>
>>>>> 1. http://n2.nabble.com/JSONP-Callback-support-tc2260544.html
>>>>>
>>>>>
>>>>> Kind regards,
>>>>> Tarjei Huse
>>>>> Mobil: 920 63 413
>>>>>
>>>>> ---------------------------------------------------------------------
>>>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>>>> <mailto:users-unsubscribe_at_jersey.dev.java.net>
>>>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>>> <mailto:users-help_at_jersey.dev.java.net>
>>>>>
>>>>
>>>>
>>>> ---------------------------------------------------------------------
>>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>>> <mailto:users-unsubscribe_at_jersey.dev.java.net>
>>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>>> <mailto:users-help_at_jersey.dev.java.net>
>>>>
>>>
>>>
>>> --
>>> Tarjei Huse
>>> Mobil: 920 63 413
>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
>>> <mailto:users-unsubscribe_at_jersey.dev.java.net>
>>> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>>> <mailto:users-help_at_jersey.dev.java.net>
>>>
>>
>
>
> --
> Tarjei Huse
> Mobil: 920 63 413
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe_at_jersey.dev.java.net
> For additional commands, e-mail: users-help_at_jersey.dev.java.net
>