Last active
August 9, 2022 15:32
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
}); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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