1
2
3
4
5
6
7
8
9
10
11
12
13
14 package com.addthis.codec.jackson;
15
16 import javax.annotation.Nonnull;
17 import javax.annotation.Syntax;
18 import javax.validation.ConstraintViolation;
19 import javax.validation.ValidationException;
20 import javax.validation.Validator;
21
22 import java.io.IOException;
23
24 import java.util.Set;
25
26 import com.addthis.codec.config.ConfigTraversingParser;
27 import com.addthis.codec.config.Configs;
28 import com.addthis.codec.plugins.PluginMap;
29 import com.addthis.codec.plugins.PluginRegistry;
30
31 import com.google.common.annotations.Beta;
32 import com.google.common.base.Preconditions;
33
34 import com.fasterxml.jackson.core.JsonProcessingException;
35 import com.fasterxml.jackson.databind.JsonNode;
36 import com.fasterxml.jackson.databind.ObjectMapper;
37 import com.typesafe.config.Config;
38 import com.typesafe.config.ConfigException;
39 import com.typesafe.config.ConfigFactory;
40 import com.typesafe.config.ConfigValue;
41
42 import io.dropwizard.validation.ConstraintViolations;
43
44
45
46
47
48
49
50 @Beta
51 public class CodecJackson {
52
53 private final ObjectMapper objectMapper;
54 private final PluginRegistry pluginRegistry;
55 private final Config globalDefaults;
56 private final Validator validator;
57
58 public CodecJackson(ObjectMapper objectMapper, PluginRegistry pluginRegistry,
59 Config globalDefaults, Validator validator) {
60 this.objectMapper = objectMapper;
61 this.pluginRegistry = pluginRegistry;
62 this.globalDefaults = globalDefaults;
63 this.validator = validator;
64 }
65
66 public CodecJackson withConfig(Config newGlobalDefaults) {
67 if (newGlobalDefaults == this.globalDefaults) {
68 return this;
69 } else {
70 PluginRegistry newPluginRegistry = new PluginRegistry(newGlobalDefaults);
71 ObjectMapper newObjectMapper = Jackson.newObjectMapper(newPluginRegistry);
72 return new CodecJackson(newObjectMapper, newPluginRegistry, newGlobalDefaults, validator);
73 }
74 }
75
76 public CodecJackson withOverrides(Config overrides) {
77 if (overrides == this.globalDefaults) {
78 return this;
79 } else {
80 PluginRegistry newPluginRegistry = pluginRegistry.withOverrides(overrides);
81 ObjectMapper newObjectMapper = Jackson.newObjectMapper(newPluginRegistry);
82 return new CodecJackson(newObjectMapper, newPluginRegistry, overrides, validator);
83 }
84 }
85
86 public ObjectMapper getObjectMapper() {
87 return objectMapper;
88 }
89
90 public PluginRegistry getPluginRegistry() {
91 return pluginRegistry;
92 }
93
94 public Config getGlobalDefaults() {
95 return globalDefaults;
96 }
97
98 public Validator getValidator() {
99 return validator;
100 }
101
102
103
104
105
106
107
108 public <T> T newDefault(@Nonnull Class<T> type) throws JsonProcessingException, IOException {
109 T value = objectMapper.treeToValue(DefaultCodecJackson.DEFAULT_MAPPER.createObjectNode(), type);
110 return validate(value);
111 }
112
113 public <T> T decodeObject(@Nonnull Class<T> type, ConfigValue configValue)
114 throws JsonProcessingException, IOException {
115 ConfigTraversingParser configParser = new ConfigTraversingParser(configValue, objectMapper);
116 return validate(configParser.readValueAs(type));
117 }
118
119 public <T> T decodeObject(@Nonnull Class<T> type, JsonNode jsonNode) throws JsonProcessingException {
120 return validate(objectMapper.treeToValue(jsonNode, type));
121 }
122
123
124
125
126 public <T> T newDefault(@Nonnull String category) throws JsonProcessingException, IOException {
127 Class<T> type = (Class<T>) pluginRegistry.byCategory().get(category).baseClass();
128 return newDefault(type);
129 }
130
131
132
133
134
135
136 public <T> T decodeObject(@Nonnull String category, @Syntax("HOCON") String configText)
137 throws JsonProcessingException, IOException {
138 PluginMap pluginMap = Preconditions.checkNotNull(pluginRegistry.asMap().get(category),
139 "could not find anything about the category %s", category);
140 Config config = ConfigFactory.parseString(configText).resolve();
141 return (T) decodeObject(pluginMap.baseClass(), config);
142 }
143
144
145
146
147
148
149 public <T> T decodeObject(@Nonnull Class<T> type, @Syntax("HOCON") String configText)
150 throws JsonProcessingException, IOException {
151 Config config = ConfigFactory.parseString(configText).resolve();
152 return decodeObject(type, config);
153 }
154
155
156
157
158
159
160 public <T> T decodeObject(@Nonnull String category, @Nonnull Config config)
161 throws JsonProcessingException, IOException {
162 PluginMap pluginMap = Preconditions.checkNotNull(pluginRegistry.asMap().get(category),
163 "could not find anything about the category %s", category);
164 return (T) decodeObject(pluginMap.baseClass(), config);
165 }
166
167
168
169
170
171
172 public <T> T decodeObject(@Nonnull Class<T> type, Config config)
173 throws JsonProcessingException, IOException {
174 return decodeObject(type, config.root());
175 }
176
177
178
179
180
181
182 public <T> T decodeObject(@Syntax("HOCON") String configText) throws JsonProcessingException, IOException {
183 Config config = ConfigFactory.parseString(configText).resolve();
184 return decodeObject(config);
185 }
186
187
188
189
190
191
192 public <T> T decodeObject(Config config) throws JsonProcessingException, IOException {
193 if (config.root().size() != 1) {
194 throw new ConfigException.Parse(config.root().origin(),
195 "config root must have exactly one key");
196 }
197 String category = config.root().keySet().iterator().next();
198 PluginMap pluginMap = pluginRegistry.asMap().get(category);
199 if (pluginMap == null) {
200 throw new ConfigException.BadValue(config.root().get(category).origin(),
201 category,
202 "top level key must be a valid category");
203 }
204 ConfigValue configValue = config.root().get(category);
205 return (T) decodeObject(pluginMap.baseClass(), configValue);
206 }
207
208 public <T> T validate(T value) {
209 Set<ConstraintViolation<T>> violations = validator.validate(value);
210 if (!violations.isEmpty()) {
211 throw new ValidationException(ConstraintViolations.<T>format(violations).toString());
212 }
213 return value;
214 }
215 }