1
2
3
4
5
6
7
8
9
10
11
12
13
14 package com.addthis.codec.jackson;
15
16 import java.io.IOException;
17
18 import java.util.Iterator;
19 import java.util.regex.Pattern;
20
21 import com.addthis.codec.annotations.Bytes;
22 import com.addthis.codec.annotations.Time;
23 import com.addthis.codec.codables.SuperCodable;
24
25 import com.fasterxml.jackson.core.JsonLocation;
26 import com.fasterxml.jackson.core.JsonParser;
27 import com.fasterxml.jackson.core.JsonProcessingException;
28 import com.fasterxml.jackson.core.JsonToken;
29 import com.fasterxml.jackson.databind.DeserializationContext;
30 import com.fasterxml.jackson.databind.JsonDeserializer;
31 import com.fasterxml.jackson.databind.JsonMappingException;
32 import com.fasterxml.jackson.databind.JsonNode;
33 import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
34 import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
35 import com.fasterxml.jackson.databind.deser.std.DelegatingDeserializer;
36 import com.fasterxml.jackson.databind.node.ObjectNode;
37 import com.fasterxml.jackson.databind.util.NameTransformer;
38
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 import io.dropwizard.util.Duration;
43 import io.dropwizard.util.Size;
44
45 public class CodecBeanDeserializer extends DelegatingDeserializer {
46 private static final Logger log = LoggerFactory.getLogger(CodecBeanDeserializer.class);
47 private static final Pattern NUMBER_UNIT = Pattern.compile("(\\d+)\\s*([^\\s\\d]+)");
48
49 private final ObjectNode fieldDefaults;
50
51 protected CodecBeanDeserializer(BeanDeserializerBase src, ObjectNode fieldDefaults) {
52 super(src);
53 this.fieldDefaults = fieldDefaults;
54 }
55
56 @Override public BeanDeserializerBase getDelegatee() {
57 return (BeanDeserializerBase) _delegatee;
58 }
59
60 @Override protected JsonDeserializer<?> newDelegatingInstance(JsonDeserializer<?> newDelegatee) {
61 return new CodecBeanDeserializer((BeanDeserializerBase) newDelegatee, fieldDefaults);
62 }
63
64 @Override
65 public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
66 JsonLocation currentLocation = jp.getTokenLocation();
67 JsonToken t = jp.getCurrentToken();
68 try {
69 if (t == JsonToken.START_OBJECT) {
70 ObjectNode objectNode = jp.readValueAsTree();
71 handleDefaultsAndRequiredAndNull(ctxt, objectNode);
72 jp = jp.getCodec().treeAsTokens(objectNode);
73 jp.nextToken();
74 } else if (t == JsonToken.END_OBJECT) {
75
76 jp.nextToken();
77 ObjectNode objectNode = ctxt.getNodeFactory().objectNode();
78 handleDefaultsAndRequiredAndNull(ctxt, objectNode);
79 jp = jp.getCodec().treeAsTokens(objectNode);
80 jp.nextToken();
81 }
82 Object value = getDelegatee().deserialize(jp, ctxt);
83 if (value instanceof SuperCodable) {
84 ((SuperCodable) value).postDecode();
85 }
86 return value;
87 } catch (JsonMappingException ex) {
88 throw Jackson.maybeImproveLocation(currentLocation, ex);
89 }
90 }
91
92 private void handleDefaultsAndRequiredAndNull(DeserializationContext ctxt, ObjectNode fieldValues)
93 throws JsonMappingException {
94 Iterator<SettableBeanProperty> propertyIterator = getDelegatee().properties();
95 while (propertyIterator.hasNext()) {
96 SettableBeanProperty prop = propertyIterator.next();
97 String propertyName = prop.getName();
98 JsonNode fieldValue = fieldValues.path(propertyName);
99 if (fieldValue.isMissingNode() || fieldValue.isNull()) {
100 if (fieldDefaults.hasNonNull(propertyName)) {
101 fieldValue = fieldDefaults.get(propertyName).deepCopy();
102 fieldValues.set(propertyName, fieldValue);
103 } else if (prop.isRequired()) {
104 throw MissingPropertyException.from(ctxt.getParser(), prop.getType().getRawClass(),
105 propertyName, getKnownPropertyNames());
106 } else if (fieldValue.isNull()
107 && (prop.getType().isPrimitive() || (prop.getValueDeserializer().getNullValue() == null))) {
108
109 fieldValues.remove(propertyName);
110 }
111 }
112 if (fieldValue.isTextual()) {
113 try {
114
115
116
117 Time time = prop.getAnnotation(Time.class);
118 if ((time != null) && NUMBER_UNIT.matcher(fieldValue.textValue()).matches()) {
119 Duration dropWizardDuration = Duration.parse(fieldValue.asText());
120 long asLong = time.value().convert(dropWizardDuration.getQuantity(), dropWizardDuration.getUnit());
121 fieldValues.put(propertyName, asLong);
122 } else if ((prop.getAnnotation(Bytes.class) != null) &&
123 NUMBER_UNIT.matcher(fieldValue.textValue()).matches()) {
124 Size dropWizardSize = Size.parse(fieldValue.asText());
125 long asLong = dropWizardSize.toBytes();
126 fieldValues.put(propertyName, asLong);
127 }
128 } catch (Throwable cause) {
129 throw JsonMappingException.wrapWithPath(cause, prop.getType().getRawClass(), propertyName);
130 }
131 }
132 }
133 }
134
135
136
137 @Override
138 public JsonDeserializer<Object> unwrappingDeserializer(NameTransformer unwrapper) {
139 return (JsonDeserializer<Object>) replaceDelegatee(getDelegatee().unwrappingDeserializer(unwrapper));
140 }
141 }