1 package cn.home1.oss.lib.common;
2
3 import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS;
4 import static java.lang.Boolean.FALSE;
5 import static java.lang.Boolean.parseBoolean;
6
7 import com.fasterxml.jackson.core.JsonGenerator;
8 import com.fasterxml.jackson.core.JsonParser;
9 import com.fasterxml.jackson.databind.DeserializationFeature;
10 import com.fasterxml.jackson.databind.MapperFeature;
11 import com.fasterxml.jackson.databind.Module;
12 import com.fasterxml.jackson.databind.ObjectMapper;
13 import com.fasterxml.jackson.databind.SerializationFeature;
14 import com.fasterxml.jackson.datatype.joda.JodaModule;
15
16 import lombok.extern.slf4j.Slf4j;
17
18 import org.slf4j.LoggerFactory;
19 import org.springframework.core.env.PropertyResolver;
20
21 import java.lang.reflect.Method;
22 import java.text.DateFormat;
23 import java.text.SimpleDateFormat;
24 import java.util.Optional;
25
26
27
28
29 public interface Jackson2Configurator<T extends Enum<T> & Jackson2Configurator<T>> {
30
31 static Object getEnumValue(final Class<?> enumType, final String name) throws ReflectiveOperationException {
32 final Object result;
33 if (enumType != null) {
34 final Method method = enumType.getDeclaredMethod("valueOf", String.class);
35 result = method.invoke(enumType, name);
36 } else {
37 result = null;
38 }
39 return result;
40 }
41
42 String JACKSON_JAXB_ENABLED = "jackson.jaxb.enabled";
43 String XMLMAPPER_CLASSNAME = "com.fasterxml.jackson.dataformat.xml.XmlMapper";
44
45 <M extends ObjectMapper> M config(PropertyResolver propertyResolver, M mapper);
46
47 default Optional<Class<?>> findClass(final String className) {
48 Class<?> classFound;
49 try {
50 classFound = Class.forName(className);
51 } catch (final ClassNotFoundException ex) {
52 LoggerFactory.getLogger(Jackson2Configurator.class).debug("{} not found", className, ex);
53 classFound = null;
54 }
55 return Optional.ofNullable(classFound);
56 }
57
58 default Optional<String> getProperty(final PropertyResolver propertyResolver, final String key) {
59 final Optional<String> result;
60 if (propertyResolver != null) {
61 final String property = propertyResolver.getProperty(key);
62 result = Optional.ofNullable(property);
63 } else {
64 result = Optional.empty();
65 }
66 return result;
67 }
68
69 default <M extends ObjectMapper> Boolean isXmlMapper(final M mapper) {
70 final Optional<Class<?>> optional = findClass(XMLMAPPER_CLASSNAME);
71 return optional.map(xmlMapperClass -> xmlMapperClass.isAssignableFrom(mapper.getClass())).orElse(FALSE);
72 }
73
74
75
76
77
78
79
80
81 @Slf4j
82 enum BuildinJackson2Configurators implements Jackson2Configurator<BuildinJackson2Configurators> {
83 JACKSON2_DEFAULT_CONFIGURATOR {
84 @Override
85 public <M extends ObjectMapper> M config(final PropertyResolver propertyResolver, final M mapper) {
86 mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
87 mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
88 mapper.configure(SerializationFeature.INDENT_OUTPUT, false);
89
90 mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
91 mapper.configure(JsonGenerator.Feature.QUOTE_NON_NUMERIC_NUMBERS, false);
92 mapper.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
93
94 if (this.isXmlMapper(mapper)) {
95 try {
96 final Object feature = getEnumValue(MapperFeature.class, "INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES");
97
98 mapper.configure((MapperFeature) feature, false);
99 } catch (final ReflectiveOperationException ex) {
100 log.info("Feature INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES not found.", ex);
101 }
102 }
103
104 return mapper;
105 }
106 },
107 JACKSON2_DATETIME_CONFIGURATOR {
108 @Override
109 public <M extends ObjectMapper> M config(final PropertyResolver propertyResolver, final M mapper) {
110 mapper.setTimeZone(Defaults.UTC_P8.toTimeZone());
111 mapper.registerModule(new JodaModule());
112
113 mapper.disable(WRITE_DATES_AS_TIMESTAMPS);
114
115
116 final DateFormat formatJdk = new SimpleDateFormat(Defaults.PATTERN_JAVA_ISO8601);
117 formatJdk.setTimeZone(Defaults.UTC_P8.toTimeZone());
118 mapper.setDateFormat(formatJdk);
119 return mapper;
120 }
121 },
122 JACKSON2_HAL_CONFIGURATOR {
123
124 private static final String MODULE_CLASS = "org.springframework.hateoas.hal.Jackson2HalModule";
125
126 @Override
127 public <M extends ObjectMapper> M config(final PropertyResolver propertyResolver, final M mapper) {
128 final Optional<Class<?>> moduleClass = this.findClass(MODULE_CLASS);
129 if (moduleClass.isPresent()) {
130
131 final Boolean isAlreadyRegisteredIn = this.isAlreadyRegisteredIn(mapper, moduleClass.get());
132 if (!isAlreadyRegisteredIn) {
133 try {
134 mapper.registerModule((Module) moduleClass.get().newInstance());
135 } catch (final ReflectiveOperationException ex) {
136 log.info("Jackson2HalModule config error", ex);
137 }
138 }
139 }
140 return mapper;
141 }
142
143 <M extends ObjectMapper> Boolean isAlreadyRegisteredIn(final M mapper, final Class<?> jackson2HalModuleClass) {
144 Boolean result;
145 try {
146 final Method isAlreadyRegisteredIn = jackson2HalModuleClass.getDeclaredMethod(
147 "isAlreadyRegisteredIn", ObjectMapper.class);
148 result = (Boolean) isAlreadyRegisteredIn.invoke(null, mapper);
149 } catch (final ReflectiveOperationException | SecurityException | IllegalArgumentException ex) {
150 log.info("Jackson2HalModule config error", ex);
151 result = FALSE;
152 }
153 return result;
154 }
155 },
156
157
158
159 JACKSON2_JAXB_ANNOTATION_CONFIGUATOR {
160
161 private static final String MODULE_CLASS = "com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule";
162 private static final String JAXB_ANNOTATION_INTROSPECTOR_CLASS =
163 "com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector";
164
165 @Override
166 public <M extends ObjectMapper> M config(final PropertyResolver propertyResolver, final M mapper) {
167 final Boolean isXmlMapper = this.isXmlMapper(mapper);
168 final String jaxbEnabledDefault = isXmlMapper ? "true" : "false";
169 final Boolean jaxbEnabled = parseBoolean(
170 this.getProperty(propertyResolver, JACKSON_JAXB_ENABLED).orElse(jaxbEnabledDefault));
171
172 final Optional<Class<?>> moduleClass = this.findClass(MODULE_CLASS);
173 if (jaxbEnabled && moduleClass.isPresent()) {
174 try {
175 final Class<?> jaxbClass = this.findClass(JAXB_ANNOTATION_INTROSPECTOR_CLASS).get();
176 final Module module = (Module) moduleClass.get().getConstructor(jaxbClass)
177 .newInstance(new Jackson2HackedJaxbAnnotationIntrospector());
178 final Class<?> enumType = this.findClass(MODULE_CLASS + "$Priority").orElse(null);
179
180 final String priorityName = isXmlMapper ? "PRIMARY" : "SECONDARY";
181 final Object priority = getEnumValue(enumType, priorityName);
182 final Method setPriorityMethod = moduleClass.get().getDeclaredMethod("setPriority", enumType);
183 setPriorityMethod.invoke(module, priority);
184
185 mapper.registerModule(module);
186 } catch (final ReflectiveOperationException ex) {
187 log.info("JaxbAnnotationModule config error", ex);
188 }
189 }
190 return mapper;
191 }
192 };
193
194 @Override
195 public abstract <M extends ObjectMapper> M config(PropertyResolver propertyResolver, M objectMapper);
196 }
197 }