1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.linecorp.centraldogma.internal;
18
19 import static com.fasterxml.jackson.databind.node.JsonNodeType.OBJECT;
20 import static com.google.common.base.Preconditions.checkArgument;
21 import static java.util.Objects.requireNonNull;
22
23 import java.io.File;
24 import java.io.IOError;
25 import java.io.IOException;
26 import java.io.Writer;
27 import java.time.Instant;
28 import java.util.Iterator;
29 import java.util.Set;
30
31 import com.fasterxml.jackson.core.JsonFactory;
32 import com.fasterxml.jackson.core.JsonGenerator;
33 import com.fasterxml.jackson.core.JsonParseException;
34 import com.fasterxml.jackson.core.JsonParser;
35 import com.fasterxml.jackson.core.JsonProcessingException;
36 import com.fasterxml.jackson.core.TreeNode;
37 import com.fasterxml.jackson.core.io.JsonStringEncoder;
38 import com.fasterxml.jackson.core.io.SegmentedStringWriter;
39 import com.fasterxml.jackson.core.type.TypeReference;
40 import com.fasterxml.jackson.core.util.DefaultIndenter;
41 import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
42 import com.fasterxml.jackson.databind.JsonMappingException;
43 import com.fasterxml.jackson.databind.JsonNode;
44 import com.fasterxml.jackson.databind.Module;
45 import com.fasterxml.jackson.databind.ObjectMapper;
46 import com.fasterxml.jackson.databind.SerializationFeature;
47 import com.fasterxml.jackson.databind.jsontype.NamedType;
48 import com.fasterxml.jackson.databind.module.SimpleModule;
49 import com.fasterxml.jackson.databind.node.JsonNodeType;
50 import com.fasterxml.jackson.databind.node.NullNode;
51 import com.fasterxml.jackson.databind.node.ObjectNode;
52 import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer;
53 import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
54 import com.google.common.collect.ImmutableList;
55 import com.google.common.collect.Iterables;
56 import com.jayway.jsonpath.Configuration;
57 import com.jayway.jsonpath.Configuration.Defaults;
58 import com.jayway.jsonpath.JsonPath;
59 import com.jayway.jsonpath.Option;
60 import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
61 import com.jayway.jsonpath.spi.json.JsonProvider;
62 import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
63 import com.jayway.jsonpath.spi.mapper.MappingProvider;
64
65 import com.linecorp.centraldogma.common.QueryExecutionException;
66 import com.linecorp.centraldogma.common.QuerySyntaxException;
67
68 public final class Jackson {
69
70 private static final ObjectMapper compactMapper = new ObjectMapper();
71 private static final ObjectMapper prettyMapper = new ObjectMapper();
72
73 static {
74
75 compactMapper.disable(SerializationFeature.INDENT_OUTPUT);
76 prettyMapper.enable(SerializationFeature.INDENT_OUTPUT);
77
78 compactMapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
79 prettyMapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS);
80
81 registerModules(new SimpleModule().addSerializer(Instant.class, InstantSerializer.INSTANCE)
82 .addDeserializer(Instant.class, InstantDeserializer.INSTANT));
83 }
84
85 private static final JsonFactory compactFactory = new JsonFactory(compactMapper);
86 private static final JsonFactory prettyFactory = new JsonFactory(prettyMapper);
87 private static final Configuration jsonPathCfg =
88 Configuration.builder()
89 .jsonProvider(new JacksonJsonNodeJsonProvider())
90 .mappingProvider(new JacksonMappingProvider(prettyMapper))
91 .build();
92
93 static {
94
95
96 if (Configuration.class.getPackage().getName().endsWith(".shaded.jsonpath")) {
97 Configuration.setDefaults(new Defaults() {
98 @Override
99 public JsonProvider jsonProvider() {
100 return jsonPathCfg.jsonProvider();
101 }
102
103 @Override
104 public Set<Option> options() {
105 return jsonPathCfg.getOptions();
106 }
107
108 @Override
109 public MappingProvider mappingProvider() {
110 return jsonPathCfg.mappingProvider();
111 }
112 });
113 }
114 }
115
116 public static final NullNode nullNode = NullNode.instance;
117
118 public static void registerModules(Module... modules) {
119 compactMapper.registerModules(modules);
120 prettyMapper.registerModules(modules);
121 }
122
123 public static void registerSubtypes(NamedType... subtypes) {
124 compactMapper.registerSubtypes(subtypes);
125 prettyMapper.registerSubtypes(subtypes);
126 }
127
128 public static void registerSubtypes(Class<?>... subtypes) {
129 compactMapper.registerSubtypes(subtypes);
130 prettyMapper.registerSubtypes(subtypes);
131 }
132
133 public static <T> T readValue(String data, Class<T> type) throws JsonParseException, JsonMappingException {
134 try {
135 return compactMapper.readValue(data, type);
136 } catch (JsonParseException | JsonMappingException e) {
137 throw e;
138 } catch (IOException e) {
139 throw new IOError(e);
140 }
141 }
142
143 public static <T> T readValue(byte[] data, Class<T> type) throws JsonParseException, JsonMappingException {
144 try {
145 return compactMapper.readValue(data, type);
146 } catch (JsonParseException | JsonMappingException e) {
147 throw e;
148 } catch (IOException e) {
149 throw new IOError(e);
150 }
151 }
152
153 public static <T> T readValue(File file, Class<T> type) throws JsonParseException, JsonMappingException {
154 try {
155 return compactMapper.readValue(file, type);
156 } catch (JsonParseException | JsonMappingException e) {
157 throw e;
158 } catch (IOException e) {
159 throw new IOError(e);
160 }
161 }
162
163 public static <T> T readValue(String data, TypeReference<T> typeReference)
164 throws JsonParseException, JsonMappingException {
165 try {
166 return compactMapper.readValue(data, typeReference);
167 } catch (JsonParseException | JsonMappingException e) {
168 throw e;
169 } catch (IOException e) {
170 throw new IOError(e);
171 }
172 }
173
174 public static <T> T readValue(byte[] data, TypeReference<T> typeReference)
175 throws JsonParseException, JsonMappingException {
176 try {
177 return compactMapper.readValue(data, typeReference);
178 } catch (JsonParseException | JsonMappingException e) {
179 throw e;
180 } catch (IOException e) {
181 throw new IOError(e);
182 }
183 }
184
185 public static <T> T readValue(File file, TypeReference<T> typeReference) throws IOException {
186 return compactMapper.readValue(file, typeReference);
187 }
188
189 public static <T> T readValue(JsonParser jp, TypeReference<T> typeReference) throws IOException {
190 return compactMapper.readValue(jp, typeReference);
191 }
192
193 public static JsonNode readTree(String data) throws JsonParseException {
194 try {
195 return compactMapper.readTree(data);
196 } catch (JsonParseException e) {
197 throw e;
198 } catch (IOException e) {
199 throw new IOError(e);
200 }
201 }
202
203 public static JsonNode readTree(byte[] data) throws JsonParseException {
204 try {
205 return compactMapper.readTree(data);
206 } catch (JsonParseException e) {
207 throw e;
208 } catch (IOException e) {
209 throw new IOError(e);
210 }
211 }
212
213 public static byte[] writeValueAsBytes(Object value) throws JsonProcessingException {
214 return compactMapper.writeValueAsBytes(value);
215 }
216
217 public static String writeValueAsString(Object value) throws JsonProcessingException {
218 return compactMapper.writeValueAsString(value);
219 }
220
221 public static String writeValueAsPrettyString(Object value) throws JsonProcessingException {
222
223
224
225 final JsonFactory factory = prettyMapper.getFactory();
226 final SegmentedStringWriter sw = new SegmentedStringWriter(factory._getBufferRecycler());
227 try {
228 final JsonGenerator g = prettyMapper.getFactory().createGenerator(sw);
229 g.setPrettyPrinter(new PrettyPrinterImpl());
230 prettyMapper.writeValue(g, value);
231 return sw.getAndClear();
232 } catch (JsonProcessingException e) {
233 throw e;
234 } catch (IOException e) {
235 throw new IOError(e);
236 }
237 }
238
239 public static <T extends JsonNode> T valueToTree(Object value) {
240 return compactMapper.valueToTree(value);
241 }
242
243 public static <T> T treeToValue(TreeNode node, Class<T> valueType)
244 throws JsonParseException, JsonMappingException {
245 try {
246 return compactMapper.treeToValue(node, valueType);
247 } catch (JsonParseException | JsonMappingException e) {
248 throw e;
249 } catch (JsonProcessingException e) {
250
251 throw new IllegalStateException(e);
252 }
253 }
254
255 public static <T> T convertValue(Object fromValue, Class<T> toValueType) {
256 return compactMapper.convertValue(fromValue, toValueType);
257 }
258
259 public static <T> T convertValue(Object fromValue, TypeReference<T> toValueTypeRef) {
260 return compactMapper.convertValue(fromValue, toValueTypeRef);
261 }
262
263 public static JsonGenerator createGenerator(Writer writer) throws IOException {
264 return compactFactory.createGenerator(writer);
265 }
266
267 public static JsonGenerator createPrettyGenerator(Writer writer) throws IOException {
268 final JsonGenerator generator = prettyFactory.createGenerator(writer);
269 generator.useDefaultPrettyPrinter();
270 return generator;
271 }
272
273 public static String textValue(JsonNode node, String defaultValue) {
274 return node != null && node.getNodeType() == JsonNodeType.STRING ? node.textValue() : defaultValue;
275 }
276
277 public static JsonNode extractTree(JsonNode jsonNode, Iterable<String> jsonPaths) {
278 for (String jsonPath : jsonPaths) {
279 jsonNode = extractTree(jsonNode, jsonPath);
280 }
281 return jsonNode;
282 }
283
284 public static JsonNode extractTree(JsonNode jsonNode, String jsonPath) {
285 requireNonNull(jsonNode, "jsonNode");
286 requireNonNull(jsonPath, "jsonPath");
287
288 final JsonPath compiledJsonPath;
289 try {
290 compiledJsonPath = JsonPath.compile(jsonPath);
291 } catch (Exception e) {
292 throw new QuerySyntaxException("invalid JSON path: " + jsonPath, e);
293 }
294
295 try {
296 return JsonPath.parse(jsonNode, jsonPathCfg)
297 .read(compiledJsonPath, JsonNode.class);
298 } catch (Exception e) {
299 throw new QueryExecutionException("JSON path evaluation failed: " + jsonPath, e);
300 }
301 }
302
303 public static String escapeText(String text) {
304 final JsonStringEncoder enc = JsonStringEncoder.getInstance();
305 return new String(enc.quoteAsString(text));
306 }
307
308 public static JsonNode mergeTree(JsonNode... jsonNodes) {
309 return mergeTree(ImmutableList.copyOf(requireNonNull(jsonNodes, "jsonNodes")));
310 }
311
312 public static JsonNode mergeTree(Iterable<JsonNode> jsonNodes) {
313 requireNonNull(jsonNodes, "jsonNodes");
314 final int size = Iterables.size(jsonNodes);
315 checkArgument(size > 0, "jsonNodes is empty.");
316 final Iterator<JsonNode> it = jsonNodes.iterator();
317 final JsonNode first = it.next();
318 JsonNode merged = first.deepCopy();
319
320 final StringBuilder fieldNameAppender = new StringBuilder("/");
321 while (it.hasNext()) {
322 final JsonNode addition = it.next();
323 merged = traverse(merged, addition, fieldNameAppender, true, true);
324 }
325
326 if (size > 2) {
327
328 traverse(first, merged, fieldNameAppender, false, true);
329 }
330 return merged;
331 }
332
333 private static JsonNode traverse(JsonNode base, JsonNode update, StringBuilder fieldNameAppender,
334 boolean isMerging, boolean isRoot) {
335 if (base.isObject() && update.isObject()) {
336 final ObjectNode baseObject = (ObjectNode) base;
337 final Iterator<String> fieldNames = update.fieldNames();
338 while (fieldNames.hasNext()) {
339 final String fieldName = fieldNames.next();
340 final JsonNode baseValue = baseObject.get(fieldName);
341 final JsonNode updateValue = update.get(fieldName);
342
343 if (baseValue == null || baseValue.isNull() || updateValue.isNull()) {
344 if (isMerging) {
345 baseObject.set(fieldName, updateValue);
346 }
347 continue;
348 }
349
350 final int length = fieldNameAppender.length();
351
352 fieldNameAppender.append(fieldName);
353 fieldNameAppender.append('/');
354 final JsonNode traversed =
355 traverse(baseValue, updateValue, fieldNameAppender, isMerging, false);
356 if (isMerging) {
357 baseObject.set(fieldName, traversed);
358 }
359
360 fieldNameAppender.delete(length, fieldNameAppender.length());
361 }
362
363 return base;
364 }
365
366 if (isRoot || (base.getNodeType() != update.getNodeType() && (!base.isNull() || !update.isNull()))) {
367 throw new QueryExecutionException("Failed to merge tree. " + fieldNameAppender +
368 " type: " + update.getNodeType() +
369 " (expected: " + (isRoot ? OBJECT : base.getNodeType()) + ')');
370 }
371
372 return update;
373 }
374
375 private Jackson() {}
376
377 private static class PrettyPrinterImpl extends DefaultPrettyPrinter {
378 private static final long serialVersionUID = 8408886209309852098L;
379
380
381
382 private static final Indenter objectIndenter = new DefaultIndenter(" ", "\n");
383
384 @SuppressWarnings("AssignmentToSuperclassField")
385 PrettyPrinterImpl() {
386 _objectFieldValueSeparatorWithSpaces = ": ";
387 _objectIndenter = objectIndenter;
388 }
389 }
390 }