Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jersey/jackson - filter properties based on query parameter

Filtering out properties with Jackson is pretty simple:

final FilterProvider filters = new SimpleFilterProvider().
    addFilter(... the name of the filter ...,
    SimpleBeanPropertyFilter.filterOutAllExcept(... enumeration of properties ...));

<object mapper>.writer(filters).writeValueAsString(... the bean ...);

I am trying to integrate this in my Jersey REST application. The API user has the possibility to filter the properties by providing a query string:

https://the-api/persons?fields=name,age,location,gender

What is the most elegant way to do this in Jersey? I could easily execute the above in my resource methods, but this somehow kills the elegance of Jersey. Also, I believe that creating a new ObjectMapper for every request will have performance penalties.

I could write a MessageBodyWriter which fetches the fields query parameter from the UriInfo context and serializes the entity to json while applying a filter based on the fields query parameter. Is this the best way to do this? Like this:

@Provider
@Produces(MediaType.APPLICATION_JSON)
public class JustTesting implements MessageBodyWriter<Object> {
    @Context
    UriInfo uriInfo;
    @Context
    JacksonJsonProvider jsonProvider;

    public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
        return MediaType.APPLICATION_JSON_TYPE.equals(mediaType);
    }

    public long getSize(Object object, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    public void writeTo(Object object, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> stringObjectMultivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException {
        final FilterProvider filters = new SimpleFilterProvider().addFilter("MyFilter", SimpleBeanPropertyFilter.filterOutAllExcept(uriInfo.getQueryParameters().getFirst("fields")));

        jsonProvider.locateMapper(aClass, mediaType).writer(filters).writeValue(outputStream, object);
    }
}

It seems to work, but I am unsure if it is smart to do it like this. I am new to the Jersey library.

like image 216
Jevo Taren Avatar asked Oct 20 '14 15:10

Jevo Taren


1 Answers

My current solution to a similar problem is to register the following servlet filter:

@Singleton
public class ViewFilter implements Filter {
    private @Inject Provider<ViewBeanPropertyFilter> filter;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException { }

    @Override
    public void destroy() { }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ObjectWriterInjector.set(new ObjectWriterModifier() {
            @Override
            public ObjectWriter modify(EndpointConfigBase<?> endpoint, MultivaluedMap<String, Object> responseHeaders, Object valueToWrite, ObjectWriter w, JsonGenerator g) throws IOException {
                return w.with(new FilterProvider() {
                    @Override
                    public BeanPropertyFilter findFilter(Object filterId) {
                        if(filterId.equals(ViewFilterJacksonModule.FILTER_NAME)) {
                            return filter.get();
                        }
                        return null;
                    }
                });
            }
        });
        chain.doFilter(request, response);
    }

    public static class ViewBeanPropertyFilter extends SimpleBeanPropertyFilter {
        private @Inject ViewManager manager;

        @Override
        protected boolean include(BeanPropertyWriter writer) {
            Class<?> cls = writer.getMember().getDeclaringClass();
            return manager.isFieldInView(cls, writer.getMember().getName());
        }

        @Override
        protected boolean include(PropertyWriter writer) {
            return true;
        }
    }
}

It's a little more gnarly than the solution you provided, but leaves the standard JacksonJsonProvider serialization in place.

It can be improved by pulling the (possibly) existing FilterProvider via m.getConfig().getFilterProvider() and delegating to it before / after your filter.

like image 81
Steve Skrla Avatar answered Nov 10 '22 14:11

Steve Skrla