1
2
3
4
5
6
7
8
9
10
11
12
13
14 package com.addthis.codec.jackson;
15
16 import javax.validation.Validator;
17
18 import java.io.IOException;
19
20 import java.util.Iterator;
21 import java.util.List;
22
23 import com.addthis.codec.plugins.PluginRegistry;
24 import com.addthis.codec.utils.ExecutorsModule;
25
26 import com.google.common.annotations.Beta;
27 import com.google.common.base.Splitter;
28 import com.google.common.collect.Lists;
29
30 import com.fasterxml.jackson.annotation.JsonInclude;
31 import com.fasterxml.jackson.core.JsonLocation;
32 import com.fasterxml.jackson.core.JsonParser;
33 import com.fasterxml.jackson.databind.JavaType;
34 import com.fasterxml.jackson.databind.JsonMappingException;
35 import com.fasterxml.jackson.databind.JsonNode;
36 import com.fasterxml.jackson.databind.ObjectMapper;
37 import com.fasterxml.jackson.databind.exc.PropertyBindingException;
38 import com.fasterxml.jackson.databind.node.ObjectNode;
39 import com.fasterxml.jackson.datatype.guava.GuavaModule;
40 import com.fasterxml.jackson.datatype.jdk7.Jdk7Module;
41 import com.fasterxml.jackson.datatype.joda.JodaModule;
42 import com.typesafe.config.Config;
43 import com.typesafe.config.ConfigList;
44 import com.typesafe.config.ConfigObject;
45 import com.typesafe.config.ConfigOrigin;
46 import com.typesafe.config.ConfigValue;
47
48 import static com.fasterxml.jackson.databind.DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY;
49 import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY;
50 import static com.fasterxml.jackson.databind.MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS;
51 import static com.fasterxml.jackson.databind.MapperFeature.INFER_PROPERTY_MUTATORS;
52 import static com.fasterxml.jackson.databind.MapperFeature.USE_GETTERS_AS_SETTERS;
53
54
55
56
57
58 @Beta
59 public final class Jackson {
60 private Jackson() {}
61
62 public static final ObjectMapper SIMPLE_MAPPER = new ObjectMapper();
63 public static final JavaType NODE_TYPE = SIMPLE_MAPPER.constructType(JsonNode.class);
64
65 public static ObjectMapper defaultMapper() {
66 return DefaultCodecJackson.DEFAULT_MAPPER;
67 }
68
69 public static CodecJackson defaultCodec() {
70 return DefaultCodecJackson.DEFAULT_CODEC;
71 }
72
73 public static Validator defaultValidator() {
74 return DefaultCodecJackson.DEFAULT_VALIDATOR;
75 }
76
77 public static ObjectMapper newObjectMapper(PluginRegistry pluginRegistry) {
78 CodecModule codecModule = new CodecModule(pluginRegistry);
79 Config globalConfig = pluginRegistry.config();
80 ObjectMapper objectMapper = new ObjectMapper();
81 toggleObjectMapperOptions(objectMapper);
82 objectMapper.registerModule(codecModule);
83 registerExtraModules(objectMapper);
84 allowCommentsAndUnquotedFields(objectMapper);
85 objectMapper.setInjectableValues(new EmptyInjectableValues());
86 return objectMapper;
87 }
88
89 public static ObjectMapper registerExtraModules(ObjectMapper objectMapper) {
90 objectMapper.registerModule(new GuavaModule());
91 objectMapper.registerModule(new Jdk7Module());
92 objectMapper.registerModule(new JodaModule());
93 objectMapper.registerModule(new ExecutorsModule());
94 return objectMapper;
95 }
96
97 public static ObjectMapper allowCommentsAndUnquotedFields(ObjectMapper objectMapper) {
98 objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
99 objectMapper.configure(JsonParser.Feature.ALLOW_YAML_COMMENTS, true);
100 objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
101 return objectMapper;
102 }
103
104 public static ObjectMapper toggleObjectMapperOptions(ObjectMapper objectMapper) {
105
106
107
108 objectMapper.disable(ALLOW_FINAL_FIELDS_AS_MUTATORS);
109
110 objectMapper.disable(USE_GETTERS_AS_SETTERS);
111
112 objectMapper.disable(INFER_PROPERTY_MUTATORS);
113
114
115 objectMapper.enable(FAIL_ON_READING_DUP_TREE_KEY);
116
117
118 objectMapper.enable(ACCEPT_SINGLE_VALUE_AS_ARRAY);
119
120
121 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
122 return objectMapper;
123 }
124
125 public static JsonNode configConverter(ConfigValue source) {
126 return SIMPLE_MAPPER.convertValue(source.unwrapped(), NODE_TYPE);
127 }
128
129 public static ObjectNode configConverter(ConfigObject source) {
130 return SIMPLE_MAPPER.convertValue(source.unwrapped(), NODE_TYPE);
131 }
132
133 public static void merge(ObjectNode primary, ObjectNode backup) {
134 Iterator<String> fieldNames = backup.fieldNames();
135 while (fieldNames.hasNext()) {
136 String fieldName = fieldNames.next();
137 JsonNode primaryValue = primary.get(fieldName);
138 if (primaryValue == null) {
139 JsonNode backupValue = backup.get(fieldName).deepCopy();
140 primary.set(fieldName, backupValue);
141 } else if (primaryValue.isObject()) {
142 JsonNode backupValue = backup.get(fieldName);
143 if (backupValue.isObject()) {
144 merge((ObjectNode) primaryValue, (ObjectNode) backupValue.deepCopy());
145 }
146 }
147 }
148 }
149
150 private static final Splitter dotSplitter = Splitter.on('.');
151
152 public static void setAt(ObjectNode root, JsonNode value, String path) {
153 if (path.indexOf('.') >= 0) {
154 Iterator<String> pathIterator = dotSplitter.split(path).iterator();
155 while (pathIterator.hasNext()) {
156 String nextPart = pathIterator.next();
157 if (pathIterator.hasNext()) {
158 root = root.with(nextPart);
159 } else {
160 root.set(nextPart, value);
161 }
162 }
163 } else {
164 root.set(path, value);
165 }
166 }
167
168 public static IOException maybeUnwrapPath(String pathToSkip, IOException cause) {
169 if ((pathToSkip != null) && (cause instanceof JsonMappingException)) {
170 JsonMappingException mappingException = (JsonMappingException) cause;
171 List<JsonMappingException.Reference> paths = mappingException.getPath();
172 if (!paths.isEmpty()) {
173 Iterator<String> pathIterator = dotSplitter.split(pathToSkip).iterator();
174 Iterator<JsonMappingException.Reference> refIterator = paths.iterator();
175 while (pathIterator.hasNext()) {
176 String pathToSkipPart = pathIterator.next();
177 if (!refIterator.hasNext()) {
178 return cause;
179 }
180 String nextRefField = refIterator.next().getFieldName();
181 if (!pathToSkipPart.equals(nextRefField)) {
182 return cause;
183 }
184 }
185 JsonMappingException unwrapped = new JsonMappingException(rootMessage(mappingException),
186 mappingException.getLocation(),
187 mappingException.getCause());
188 if (refIterator.hasNext()) {
189 List<JsonMappingException.Reference> remainingRefs = Lists.newArrayList(refIterator);
190 for (JsonMappingException.Reference reference : Lists.reverse(remainingRefs)) {
191 unwrapped.prependPath(reference);
192 }
193 }
194 return unwrapped;
195 }
196 }
197 return cause;
198 }
199
200 public static boolean isRealLocation(JsonLocation jsonLocation) {
201 return (jsonLocation != null) && (jsonLocation != JsonLocation.NA);
202 }
203
204 public static JsonMappingException maybeImproveLocation(JsonLocation wrapLoc, JsonMappingException cause) {
205 JsonLocation exLoc = cause.getLocation();
206 if (isRealLocation(wrapLoc) && !isRealLocation(exLoc)) {
207 if (wrapLoc.getSourceRef() instanceof ConfigValue) {
208 ConfigValue locRef = (ConfigValue) wrapLoc.getSourceRef();
209 List<JsonMappingException.Reference> paths = cause.getPath();
210 for (JsonMappingException.Reference path : paths) {
211 if (locRef instanceof ConfigObject) {
212 String fieldName = path.getFieldName();
213 ConfigObject locRefObject = (ConfigObject) locRef;
214 if (locRefObject.containsKey(fieldName)) {
215 locRef = locRefObject.get(fieldName);
216 } else {
217 break;
218 }
219 } else if (locRef instanceof ConfigList) {
220 int fieldIndex = path.getIndex();
221 ConfigList locRefList = (ConfigList) locRef;
222 if ((fieldIndex >= 0) && (locRefList.size() > fieldIndex)) {
223 locRef = locRefList.get(fieldIndex);
224 } else {
225 break;
226 }
227 } else {
228 break;
229 }
230 }
231 if (locRef != wrapLoc.getSourceRef()) {
232 wrapLoc = fromConfigValue(locRef);
233 }
234 }
235 List<JsonMappingException.Reference> paths = Lists.reverse(cause.getPath());
236 if (!paths.isEmpty()) {
237 JsonMappingException withLoc = new JsonMappingException(rootMessage(cause), wrapLoc, cause);
238 for (JsonMappingException.Reference path : paths) {
239 withLoc.prependPath(path);
240 }
241 return withLoc;
242 } else {
243 return new JsonMappingException(rootMessage(cause), wrapLoc, cause);
244 }
245 }
246 return cause;
247 }
248
249 public static String rootMessage(JsonMappingException ex) {
250 String rootMessage = ex.getOriginalMessage();
251 if (ex instanceof PropertyBindingException) {
252 String suffix = ((PropertyBindingException) ex).getMessageSuffix();
253 if (rootMessage == null) {
254 return suffix;
255 } else if (suffix != null) {
256 return rootMessage + suffix;
257 }
258 }
259 return rootMessage;
260 }
261
262 public static JsonLocation fromConfigValue(ConfigValue configValue) {
263 ConfigOrigin configOrigin = configValue.origin();
264 return new JsonLocation(configValue, -1, configOrigin.lineNumber(), -1);
265 }
266
267 public static JsonNode pathAt(ObjectNode root, String path) {
268 if (path.indexOf('.') >= 0) {
269 JsonNode returnNode = root;
270 for (String nextPart : dotSplitter.split(path)) {
271 returnNode = returnNode.path(nextPart);
272 }
273 return returnNode;
274 } else {
275 return root.path(path);
276 }
277 }
278 }