package org.openmhealth.reference.util;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.BeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter.SerializeExceptFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
/**
* <p>
* A custom ObjectMapper for Open mHealth that adds functionality like
* filtering fields on HTTP-level serialization.
* </p>
*
* @author John Jenkins
*/
public class OmhObjectMapper extends ObjectMapper {
/**
* <p>
* A custom annotation for fields that should not be serialized by Jackson
* when their encapsulating object is being serialized by Spring.
* </p>
*
* @author John Jenkins
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public static @interface JacksonFieldFilter {
/**
* The filter group to which the field belongs. All fields that belong
* to the same class should have the same filter group name and that
* class should be annotated with {@link JsonFilter}, which specifies
* the same group name.
*
* @return The filter group to which the field belongs.
*/
String value();
}
/**
* <p>
* The custom filter that allows for the addition of new field names.
* </p>
*
* @author John Jenkins
*/
private static class ExtendableSerializeExceptFilter
extends SerializeExceptFilter {
/**
* Creates a new filter with the field name.
*
* @param fieldName
* The first field for this filter.
*/
public ExtendableSerializeExceptFilter(final String fieldName) {
super(new HashSet<String>(Arrays.asList(fieldName)));
}
/**
* Adds another field to this filter.
*
* @param fieldName
* The new field.
*/
public void addField(final String fieldName) {
_propertiesToExclude.add(fieldName);
}
}
/**
* A default version UID to use when serializing an instance of this class.
*/
private static final long serialVersionUID = 1L;
/**
* The set of private fields that should never be serialized.
*/
private static final SimpleFilterProvider FILTER_PROVIDER =
new SimpleFilterProvider();
/**
* Creates the object mapper and initializes the filters. This is private
* as it should only ever be called by Spring via reflection.
*/
private OmhObjectMapper() {
// Ensure that unknown fields are ignored.
FILTER_PROVIDER.setFailOnUnknownId(false);
// Save the FilterProvider in this ObjectMapper.
setFilters(FILTER_PROVIDER);
}
/**
* Registers a new field filter and ensures that its desired fields are
* never serialized.
*
* @param filter
* The filter to add to this ObjectMapper.
*/
public static synchronized void register(final Class<?> filterClass) {
// Sanitize the input.
if(filterClass == null) {
throw new IllegalArgumentException("The filter class is null.");
}
// Cycle through each of the class's fields.
for(Field field : filterClass.getFields()) {
// Determine if that field has a JacksonFieldFilter annotation.
JacksonFieldFilter filter =
field.getAnnotation(JacksonFieldFilter.class);
// If it does have the annotation.
if(filter != null) {
// Get the serialized field name.
String fieldName;
JsonProperty jsonProperty =
field.getAnnotation(JsonProperty.class);
if(jsonProperty == null) {
fieldName = field.getName();
}
else {
fieldName = jsonProperty.value();
}
// Get the given group name.
String filterGroup = filter.value();
// Get the existing filter, if one exists.
BeanPropertyFilter propertyFilter =
FILTER_PROVIDER.findFilter(filterGroup);
// If no such filter exists, update the filter provider with a
// new one.
if(propertyFilter == null) {
FILTER_PROVIDER
.addFilter(
filterGroup,
new ExtendableSerializeExceptFilter(fieldName));
}
else {
// Safely cast the filter as we are the only maintainers of
// it, and this is the only type of filter we use.
ExtendableSerializeExceptFilter amenableFilter =
(ExtendableSerializeExceptFilter) propertyFilter;
// Add the field.
amenableFilter.addField(fieldName);
}
}
}
}
}