Skip to content

Instantly share code, notes, and snippets.

@oillio
Last active August 9, 2022 15:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oillio/1c1845059caf47527f94202bf14b2dca to your computer and use it in GitHub Desktop.
Save oillio/1c1845059caf47527f94202bf14b2dca to your computer and use it in GitHub Desktop.
A workaround to use, in Dropwizard, entity filtering functionality provided by org.glassfish.jersey.media:jersey-media-json-jackson
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import lombok.extern.slf4j.Slf4j;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.jersey.CommonProperties;
import org.glassfish.jersey.ServiceLocatorProvider;
import org.glassfish.jersey.internal.InternalProperties;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.jackson.internal.JacksonFilteringFeature;
import org.glassfish.jersey.message.filtering.EntityFilteringFeature;
import org.glassfish.jersey.message.filtering.spi.ObjectProvider;
import javax.inject.Provider;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
/**
* Activates Entity filtering in a way compatible with the way dropwizard configures the Jackson provider
*/
@Slf4j
public class DropwizardEntityFilteringFeature implements Feature {
private static final String JSON_FEATURE = DropwizardEntityFilteringFeature.class.getSimpleName();
private final ObjectMapper mapper;
public DropwizardEntityFilteringFeature(ObjectMapper mapper) {
this.mapper = mapper;
}
@Override
public boolean configure(final FeatureContext context) {
final Configuration config = context.getConfiguration();
// No filtering has been configured.
if (!EntityFilteringFeature.enabled(config)) {
log.warn("DropwizardEntityFilteringFeature registered, but no filtering functionality specifid.");
return false;
}
final String jsonFeature = CommonProperties.getValue(config.getProperties(), config.getRuntimeType(),
InternalProperties.JSON_FEATURE, JSON_FEATURE, String.class);
// Other JSON providers registered.
if (!JSON_FEATURE.equalsIgnoreCase(jsonFeature)) {
log.warn("DropwizardEntityFilteringFeature registerd, but different {} configured. Configured feature: {}", InternalProperties.JSON_FEATURE, jsonFeature);
return false;
}
// Disable other JSON providers.
context.property(PropertiesHelper.getPropertyNameForRuntime(InternalProperties.JSON_FEATURE, config.getRuntimeType()),
JSON_FEATURE);
ServiceLocator locator = ServiceLocatorProvider.getServiceLocator(context);
Provider<ObjectProvider<FilterProvider>> provider = locator.getService((new TypeLiteral<Provider<ObjectProvider<FilterProvider>>>() {}).getType());
context.register(JacksonFilteringFeature.class);
// Dropwizard will register a provider as well. Set a high priority for this in order to override the Dropwizard provider.
context.register(new EntityFilteringProvider(mapper, provider),100);
return true;
}
}
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.introspect.*;
import com.fasterxml.jackson.databind.ser.BeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.jaxrs.cfg.EndpointConfigBase;
import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterInjector;
import com.fasterxml.jackson.jaxrs.cfg.ObjectWriterModifier;
import com.fasterxml.jackson.jaxrs.json.JsonEndpointConfig;
import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.message.filtering.spi.ObjectProvider;
import javax.inject.Provider;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
public class EntityFilteringProvider extends JacksonMessageBodyProvider {
public EntityFilteringProvider(ObjectMapper mapper, Provider<ObjectProvider<FilterProvider>> filterProvider) {
super(mapper);
this.provider = filterProvider;
}
// Everything below this point was lifted directly from org.glassfish.jersey.jackson.internal.FilteringJacksonJaxbJsonProvider
// (org.glassfish.jersey.media:jersey-media-json-jackson:2.22.2)
// I would have preferred to inherit from this and re-implement JacksonMessageBodyProvider, but it is
// is final. So nothing to do.
private Provider<ObjectProvider<FilterProvider>> provider = null;
@Override
protected JsonEndpointConfig _configForWriting(final ObjectMapper mapper, final Annotation[] annotations,
final Class<?> defaultView) {
final AnnotationIntrospector customIntrospector = mapper.getSerializationConfig().getAnnotationIntrospector();
// Set the custom (user) introspector to be the primary one.
final ObjectMapper filteringMapper = mapper.setAnnotationIntrospector(AnnotationIntrospector.pair(customIntrospector,
new JacksonAnnotationIntrospector() {
@Override
public Object findFilterId(final Annotated a) {
final Object filterId = super.findFilterId(a);
if (filterId != null) {
return filterId;
}
if (a instanceof AnnotatedMethod) {
final Method method = ((AnnotatedMethod) a).getAnnotated();
// Interested only in getters - trying to obtain "field" name from them.
if (ReflectionHelper.isGetter(method)) {
return ReflectionHelper.getPropertyName(method);
}
}
if (a instanceof AnnotatedField || a instanceof AnnotatedClass) {
return a.getName();
}
return null;
}
}));
return super._configForWriting(filteringMapper, annotations, defaultView);
}
@Override
public void writeTo(final Object value,
final Class<?> type,
final Type genericType,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap<String, Object> httpHeaders,
final OutputStream entityStream) throws IOException {
final FilterProvider filterProvider = provider.get().getFilteringObject(genericType, true, annotations);
if (filterProvider != null) {
ObjectWriterInjector.set(new FilteringObjectWriterModifier(filterProvider, ObjectWriterInjector.getAndClear()));
}
super.writeTo(value, type, genericType, annotations, mediaType, httpHeaders, entityStream);
}
private static final class FilteringObjectWriterModifier extends ObjectWriterModifier {
private final ObjectWriterModifier original;
private final FilterProvider filterProvider;
private FilteringObjectWriterModifier(final FilterProvider filterProvider, final ObjectWriterModifier original) {
this.original = original;
this.filterProvider = filterProvider;
}
@Override
public ObjectWriter modify(final EndpointConfigBase<?> endpoint,
final MultivaluedMap<String, Object> responseHeaders,
final Object valueToWrite,
final ObjectWriter w,
final JsonGenerator g) throws IOException {
final ObjectWriter writer = original == null ? w : original.modify(endpoint, responseHeaders, valueToWrite, w, g);
final FilterProvider customFilterProvider = writer.getConfig().getFilterProvider();
// Try the custom (user) filter provider first.
return customFilterProvider == null
? writer.with(filterProvider)
: writer.with(new FilterProvider() {
@Override
public BeanPropertyFilter findFilter(final Object filterId) {
return customFilterProvider.findFilter(filterId);
}
@Override
public PropertyFilter findPropertyFilter(final Object filterId, final Object valueToFilter) {
final PropertyFilter filter = customFilterProvider.findPropertyFilter(filterId, valueToFilter);
if (filter != null) {
return filter;
}
return filterProvider.findPropertyFilter(filterId, valueToFilter);
}
});
}
}
}
// For example, if you want to activate selectable entity filtering (provided by org.glassfish.jersey.ext:jersey-entity-filtering)
// from a Dropwizard bundle
environment.jersey().register(SelectableEntityFilteringFeature.class);
environment.jersey().property(SelectableEntityFilteringFeature.QUERY_PARAM_NAME, "select");
environment.jersey().register(new DropwizardEntityFilteringFeature(environment.getObjectMapper()));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment