View Javadoc
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   * Created by zhanghaolun on 16/7/28.
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     * Build-in jackson2 configurators.
78     *
79     * @author zhanghaolun
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              // see: https://github.com/FasterXML/jackson-databind/issues/1218
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         // Jdk8Module ?
113         mapper.disable(WRITE_DATES_AS_TIMESTAMPS);
114         // disable WRITE_DATES_WITH_ZONE_ID ?
115         // ISODateTimeFormat.basicDateTime()
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           // need HalHandlerInstantiator or lead to exception on data-rest request
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      * see: http://wiki.fasterxml.com/JacksonJAXBAnnotations see: https://github.com/FasterXML/jackson-module-jaxb-annotations
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 }