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