|
1 | 1 | /*
|
2 |
| - * Copyright 2020 Netflix, Inc. |
3 |
| - * <p> |
4 |
| - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
5 |
| - * the License. You may obtain a copy of the License at |
6 |
| - * <p> |
7 |
| - * http://www.apache.org/licenses/LICENSE-2.0 |
8 |
| - * <p> |
9 |
| - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
10 |
| - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
11 |
| - * specific language governing permissions and limitations under the License. |
| 2 | + * Copyright 2021 Netflix, Inc. |
| 3 | + * <p> |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with |
| 5 | + * the License. You may obtain a copy of the License at |
| 6 | + * <p> |
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | + * <p> |
| 9 | + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on |
| 10 | + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the |
| 11 | + * specific language governing permissions and limitations under the License. |
12 | 12 | */
|
13 | 13 | package com.netflix.conductor.common.config;
|
14 | 14 |
|
15 | 15 | import com.fasterxml.jackson.annotation.JsonInclude;
|
16 |
| -import com.fasterxml.jackson.core.JsonGenerator; |
17 |
| -import com.fasterxml.jackson.core.JsonParser; |
18 |
| -import com.fasterxml.jackson.databind.DeserializationContext; |
19 | 16 | import com.fasterxml.jackson.databind.DeserializationFeature;
|
20 |
| -import com.fasterxml.jackson.databind.JsonDeserializer; |
21 |
| -import com.fasterxml.jackson.databind.JsonNode; |
22 |
| -import com.fasterxml.jackson.databind.JsonSerializer; |
23 | 17 | import com.fasterxml.jackson.databind.ObjectMapper;
|
24 |
| -import com.fasterxml.jackson.databind.SerializerProvider; |
25 |
| -import com.fasterxml.jackson.databind.module.SimpleModule; |
26 |
| -import com.google.protobuf.Any; |
27 |
| -import com.google.protobuf.ByteString; |
28 |
| -import com.google.protobuf.Message; |
29 |
| - |
30 |
| -import java.io.IOException; |
| 18 | +import com.netflix.conductor.common.jackson.JsonProtoModule; |
31 | 19 |
|
| 20 | +/** |
| 21 | + * A Factory class for creating a customized {@link ObjectMapper}. This is only used by the |
| 22 | + * conductor-client module and tests that rely on {@link ObjectMapper}. |
| 23 | + * See TestObjectMapperConfiguration. |
| 24 | + */ |
32 | 25 | public class ObjectMapperProvider {
|
33 | 26 |
|
34 | 27 | /**
|
35 |
| - * JsonProtoModule can be registered into an {@link ObjectMapper} to enable the serialization and deserialization of |
36 |
| - * ProtoBuf objects from/to JSON. |
37 |
| - * <p> |
38 |
| - * Right now this module only provides (de)serialization for the {@link Any} ProtoBuf type, as this is the only |
39 |
| - * ProtoBuf object which we're currently exposing through the REST API. |
40 |
| - * <p> |
41 |
| - * {@see AnySerializer}, {@see AnyDeserializer} |
| 28 | + * The customizations in this method are configured using {@link org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration} |
| 29 | + * |
| 30 | + * Customizations are spread across, |
| 31 | + * 1. {@link ObjectMapperBuilderConfiguration} |
| 32 | + * 2. {@link ObjectMapperConfiguration} |
| 33 | + * 3. {@link JsonProtoModule} |
| 34 | + * |
| 35 | + * IMPORTANT: Changes in this method need to be also performed in the default {@link ObjectMapper} |
| 36 | + * that Spring Boot creates. |
| 37 | + * |
| 38 | + * @see org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration |
42 | 39 | */
|
43 |
| - private static class JsonProtoModule extends SimpleModule { |
44 |
| - |
45 |
| - private final static String JSON_TYPE = "@type"; |
46 |
| - private final static String JSON_VALUE = "@value"; |
47 |
| - |
48 |
| - /** |
49 |
| - * AnySerializer converts a ProtoBuf {@link Any} object into its JSON representation. |
50 |
| - * <p> |
51 |
| - * This is <b>not</b> a canonical ProtoBuf JSON representation. Let us explain what we're trying to accomplish |
52 |
| - * here: |
53 |
| - * <p> |
54 |
| - * The {@link Any} ProtoBuf message is a type in the PB standard library that can store any other arbitrary |
55 |
| - * ProtoBuf message in a type-safe way, even when the server has no knowledge of the schema of the stored |
56 |
| - * message. |
57 |
| - * <p> |
58 |
| - * It accomplishes this by storing a tuple of information: an URL-like type declaration for the stored message, |
59 |
| - * and the serialized binary encoding of the stored message itself. Language specific implementations of |
60 |
| - * ProtoBuf provide helper methods to encode and decode arbitrary messages into an {@link Any} object ({@link |
61 |
| - * Any#pack(Message)} in Java). |
62 |
| - * <p> |
63 |
| - * We want to expose these {@link Any} objects in the REST API because they've been introduced as part of the |
64 |
| - * new GRPC interface to Conductor, but unfortunately we cannot encode them using their canonical ProtoBuf JSON |
65 |
| - * encoding. According to the docs: |
66 |
| - * <p> |
67 |
| - * The JSON representation of an `Any` value uses the regular representation of the deserialized, embedded |
68 |
| - * message, with an additional field `@type` which contains the type URL. Example: |
69 |
| - * <p> |
70 |
| - * package google.profile; message Person { string first_name = 1; string last_name = 2; } { "@type": |
71 |
| - * "type.googleapis.com/google.profile.Person", "firstName": <string>, "lastName": <string> } |
72 |
| - * <p> |
73 |
| - * In order to accomplish this representation, the PB-JSON encoder needs to have knowledge of all the ProtoBuf |
74 |
| - * messages that could be serialized inside the {@link Any} message. This is not possible to accomplish inside |
75 |
| - * the Conductor server, which is simply passing through arbitrary payloads from/to clients. |
76 |
| - * <p> |
77 |
| - * Consequently, to actually expose the Message through the REST API, we must create a custom encoding that |
78 |
| - * contains the raw data of the serialized message, as we are not able to deserialize it on the server. We |
79 |
| - * simply return a dictionary with '@type' and '@value' keys, where '@type' is identical to the canonical |
80 |
| - * representation, but '@value' contains a base64 encoded string with the binary data of the serialized |
81 |
| - * message. |
82 |
| - * <p> |
83 |
| - * Since all the provided Conductor clients are required to know this encoding, it's always possible to re-build |
84 |
| - * the original {@link Any} message regardless of the client's language. |
85 |
| - * <p> |
86 |
| - * {@see AnyDeserializer} |
87 |
| - */ |
88 |
| - @SuppressWarnings("InnerClassMayBeStatic") |
89 |
| - protected class AnySerializer extends JsonSerializer<Any> { |
90 |
| - |
91 |
| - @Override |
92 |
| - public void serialize(Any value, JsonGenerator jgen, SerializerProvider provider) |
93 |
| - throws IOException { |
94 |
| - jgen.writeStartObject(); |
95 |
| - jgen.writeStringField(JSON_TYPE, value.getTypeUrl()); |
96 |
| - jgen.writeBinaryField(JSON_VALUE, value.getValue().toByteArray()); |
97 |
| - jgen.writeEndObject(); |
98 |
| - } |
99 |
| - } |
100 |
| - |
101 |
| - /** |
102 |
| - * AnyDeserializer converts the custom JSON representation of an {@link Any} value into its original form. |
103 |
| - * <p> |
104 |
| - * {@see AnySerializer} for details on this representation. |
105 |
| - */ |
106 |
| - @SuppressWarnings("InnerClassMayBeStatic") |
107 |
| - protected class AnyDeserializer extends JsonDeserializer<Any> { |
108 |
| - |
109 |
| - @Override |
110 |
| - public Any deserialize(JsonParser p, DeserializationContext ctxt) |
111 |
| - throws IOException { |
112 |
| - JsonNode root = p.getCodec().readTree(p); |
113 |
| - JsonNode type = root.get(JSON_TYPE); |
114 |
| - JsonNode value = root.get(JSON_VALUE); |
115 |
| - |
116 |
| - if (type == null || !type.isTextual()) { |
117 |
| - ctxt.reportMappingException("invalid '@type' field when deserializing ProtoBuf Any object"); |
118 |
| - } |
119 |
| - |
120 |
| - if (value == null || !value.isTextual()) { |
121 |
| - ctxt.reportMappingException("invalid '@value' field when deserializing ProtoBuf Any object"); |
122 |
| - } |
123 |
| - |
124 |
| - return Any.newBuilder() |
125 |
| - .setTypeUrl(type.textValue()) |
126 |
| - .setValue(ByteString.copyFrom(value.binaryValue())) |
127 |
| - .build(); |
128 |
| - } |
129 |
| - } |
130 |
| - |
131 |
| - public JsonProtoModule() { |
132 |
| - super("ConductorJsonProtoModule"); |
133 |
| - addSerializer(Any.class, new AnySerializer()); |
134 |
| - addDeserializer(Any.class, new AnyDeserializer()); |
135 |
| - } |
136 |
| - } |
137 |
| - |
138 | 40 | public ObjectMapper getObjectMapper() {
|
139 | 41 | final ObjectMapper objectMapper = new ObjectMapper();
|
140 | 42 | objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
|
0 commit comments