Jackson2Configurator.java
package cn.home1.oss.lib.common;
import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.parseBoolean;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.PropertyResolver;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Optional;
/**
* Created by zhanghaolun on 16/7/28.
*/
public interface Jackson2Configurator<T extends Enum<T> & Jackson2Configurator<T>> {
static Object getEnumValue(final Class<?> enumType, final String name) throws ReflectiveOperationException {
final Object result;
if (enumType != null) {
final Method method = enumType.getDeclaredMethod("valueOf", String.class);
result = method.invoke(enumType, name);
} else {
result = null;
}
return result;
}
String JACKSON_JAXB_ENABLED = "jackson.jaxb.enabled";
String XMLMAPPER_CLASSNAME = "com.fasterxml.jackson.dataformat.xml.XmlMapper";
<M extends ObjectMapper> M config(PropertyResolver propertyResolver, M mapper);
default Optional<Class<?>> findClass(final String className) {
Class<?> classFound;
try {
classFound = Class.forName(className);
} catch (final ClassNotFoundException ex) {
LoggerFactory.getLogger(Jackson2Configurator.class).debug("{} not found", className, ex);
classFound = null;
}
return Optional.ofNullable(classFound);
}
default Optional<String> getProperty(final PropertyResolver propertyResolver, final String key) {
final Optional<String> result;
if (propertyResolver != null) {
final String property = propertyResolver.getProperty(key);
result = Optional.ofNullable(property);
} else {
result = Optional.empty();
}
return result;
}
default <M extends ObjectMapper> Boolean isXmlMapper(final M mapper) {
final Optional<Class<?>> optional = findClass(XMLMAPPER_CLASSNAME);
return optional.map(xmlMapperClass -> xmlMapperClass.isAssignableFrom(mapper.getClass())).orElse(FALSE);
}
// ---------------------------------------- ----------------------------------------
/**
* Build-in jackson2 configurators.
*
* @author zhanghaolun
*/
@Slf4j
enum BuildinJackson2Configurators implements Jackson2Configurator<BuildinJackson2Configurators> {
JACKSON2_DEFAULT_CONFIGURATOR {
@Override
public <M extends ObjectMapper> M config(final PropertyResolver propertyResolver, final M mapper) {
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
mapper.configure(SerializationFeature.INDENT_OUTPUT, false);
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
mapper.configure(JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS, false);
mapper.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
if (this.isXmlMapper(mapper)) {
try {
final Object feature = getEnumValue(MapperFeature.class, "INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES");
// see: https://github.com/FasterXML/jackson-databind/issues/1218
mapper.configure((MapperFeature) feature, false);
} catch (final ReflectiveOperationException ex) {
log.info("Feature INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES not found.", ex);
}
}
return mapper;
}
},
JACKSON2_DATETIME_CONFIGURATOR {
@Override
public <M extends ObjectMapper> M config(final PropertyResolver propertyResolver, final M mapper) {
mapper.setTimeZone(Defaults.UTC_P8.toTimeZone());
mapper.registerModule(new JodaModule());
// Jdk8Module ?
mapper.disable(WRITE_DATES_AS_TIMESTAMPS);
// disable WRITE_DATES_WITH_ZONE_ID ?
// ISODateTimeFormat.basicDateTime()
final DateFormat formatJdk = new SimpleDateFormat(Defaults.PATTERN_JAVA_ISO8601);
formatJdk.setTimeZone(Defaults.UTC_P8.toTimeZone());
mapper.setDateFormat(formatJdk);
return mapper;
}
},
JACKSON2_HAL_CONFIGURATOR {
private static final String MODULE_CLASS = "org.springframework.hateoas.hal.Jackson2HalModule";
@Override
public <M extends ObjectMapper> M config(final PropertyResolver propertyResolver, final M mapper) {
final Optional<Class<?>> moduleClass = this.findClass(MODULE_CLASS);
if (moduleClass.isPresent()) {
// need HalHandlerInstantiator or lead to exception on data-rest request
final Boolean isAlreadyRegisteredIn = this.isAlreadyRegisteredIn(mapper, moduleClass.get());
if (!isAlreadyRegisteredIn) {
try {
mapper.registerModule((Module) moduleClass.get().newInstance());
} catch (final ReflectiveOperationException ex) {
log.info("Jackson2HalModule config error", ex);
}
}
}
return mapper;
}
<M extends ObjectMapper> Boolean isAlreadyRegisteredIn(final M mapper, final Class<?> jackson2HalModuleClass) {
Boolean result;
try {
final Method isAlreadyRegisteredIn = jackson2HalModuleClass.getDeclaredMethod( //
"isAlreadyRegisteredIn", ObjectMapper.class);
result = (Boolean) isAlreadyRegisteredIn.invoke(null, mapper);
} catch (final ReflectiveOperationException | SecurityException | IllegalArgumentException ex) {
log.info("Jackson2HalModule config error", ex);
result = FALSE;
}
return result;
}
},
/**
* see: http://wiki.fasterxml.com/JacksonJAXBAnnotations see: https://github.com/FasterXML/jackson-module-jaxb-annotations
*/
JACKSON2_JAXB_ANNOTATION_CONFIGUATOR {
private static final String MODULE_CLASS = "com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule";
private static final String JAXB_ANNOTATION_INTROSPECTOR_CLASS = //
"com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector";
@Override
public <M extends ObjectMapper> M config(final PropertyResolver propertyResolver, final M mapper) {
final Boolean isXmlMapper = this.isXmlMapper(mapper);
final String jaxbEnabledDefault = isXmlMapper ? "true" : "false";
final Boolean jaxbEnabled = parseBoolean( //
this.getProperty(propertyResolver, JACKSON_JAXB_ENABLED).orElse(jaxbEnabledDefault));
final Optional<Class<?>> moduleClass = this.findClass(MODULE_CLASS);
if (jaxbEnabled && moduleClass.isPresent()) {
try {
final Class<?> jaxbClass = this.findClass(JAXB_ANNOTATION_INTROSPECTOR_CLASS).get();
final Module module = (Module) moduleClass.get().getConstructor(jaxbClass) //
.newInstance(new Jackson2HackedJaxbAnnotationIntrospector());
final Class<?> enumType = this.findClass(MODULE_CLASS + "$Priority").orElse(null);
final String priorityName = isXmlMapper ? "PRIMARY" : "SECONDARY";
final Object priority = getEnumValue(enumType, priorityName);
final Method setPriorityMethod = moduleClass.get().getDeclaredMethod("setPriority", enumType);
setPriorityMethod.invoke(module, priority);
mapper.registerModule(module);
} catch (final ReflectiveOperationException ex) {
log.info("JaxbAnnotationModule config error", ex);
}
}
return mapper;
}
};
@Override
public abstract <M extends ObjectMapper> M config(PropertyResolver propertyResolver, M objectMapper);
}
}