1 package com.fasterxml.jackson.databind.jsonschema;
2 
3 import java.math.BigDecimal;
4 import java.math.BigInteger;
5 import java.util.*;
6 import java.util.concurrent.atomic.AtomicReference;
7 
8 import com.fasterxml.jackson.annotation.JsonCreator;
9 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
10 import com.fasterxml.jackson.annotation.JsonValue;
11 import com.fasterxml.jackson.core.JsonParser.NumberType;
12 import com.fasterxml.jackson.databind.*;
13 import com.fasterxml.jackson.databind.jsonFormatVisitors.*;
14 import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
15 import com.fasterxml.jackson.databind.ser.std.StdSerializer;
16 
17 /**
18  * Basic tests to exercise low-level support added for JSON Schema module and
19  * other modules that use type introspection.
20  */
21 public class NewSchemaTest extends BaseMapTest
22 {
23     enum TestEnum {
24         A, B, C;
25 
26         @Override
toString()27         public String toString() {
28             return "ToString:"+name();
29         }
30     }
31 
32     enum TestEnumWithJsonValue {
33         A, B, C;
34 
35         @JsonValue
forSerialize()36         public String forSerialize() {
37             return "value-"+name();
38         }
39     }
40 
41     // silly little class to exercise basic traversal
42     static class POJO {
43         public List<POJO> children;
44         public POJO[] childOrdering;
45         public Map<String, java.util.Date> times;
46         public Map<String,Integer> conversions;
47 
48         public EnumMap<TestEnum,Double> weights;
49     }
50 
51     static class POJOWithScalars {
52         public boolean boo;
53         public byte b;
54         public char c;
55         public short s;
56         public int i;
57         public long l;
58         public float f;
59         public double d;
60 
61         public byte[] arrayBoo;
62         public byte[] arrayb;
63         public char[] arrayc;
64         public short[] arrays;
65         public int[] arrayi;
66         public long[] arrayl;
67         public float[] arrayf;
68         public double[] arrayd;
69 
70         public Boolean Boo;
71         public Byte B;
72         public Character C;
73         public Short S;
74         public Integer I;
75         public Long L;
76         public Float F;
77         public Double D;
78 
79         public TestEnum en;
80         public String str;
81         public String[] strs;
82         public java.util.Date date;
83         public java.util.Calendar calendar;
84     }
85 
86     static class POJOWithRefs {
87         public AtomicReference<POJO> maybePOJO;
88 
89         public AtomicReference<String> maybeString;
90     }
91 
92     // [databind#1793]
93     static class POJOWithJsonValue {
94         private Point[] value;
95 
96         @JsonCreator(mode=JsonCreator.Mode.DELEGATING)
POJOWithJsonValue(Point[] v)97         public POJOWithJsonValue(Point[] v) { value = v; }
98 
99         @JsonValue
serialization()100         public Point[] serialization() { return value; }
101     }
102 
103     @JsonPropertyOrder({ "dec", "bigInt" })
104     static class Numbers {
105         public BigDecimal dec;
106         public BigInteger bigInt;
107     }
108 
109     static class BogusJsonFormatVisitorWrapper
110         extends JsonFormatVisitorWrapper.Base
111     {
112         // Implement handlers just to get more exercise...
113 
114         @Override
expectObjectFormat(JavaType type)115         public JsonObjectFormatVisitor expectObjectFormat(JavaType type) {
116             return new JsonObjectFormatVisitor.Base(getProvider()) {
117                 @Override
118                 public void property(BeanProperty prop) throws JsonMappingException {
119                     _visit(prop);
120                 }
121 
122                 @Override
123                 public void property(String name, JsonFormatVisitable handler,
124                         JavaType propertyTypeHint) { }
125 
126                 @Override
127                 public void optionalProperty(BeanProperty prop) throws JsonMappingException {
128                     _visit(prop);
129                 }
130 
131                 @Override
132                 public void optionalProperty(String name, JsonFormatVisitable handler,
133                         JavaType propertyTypeHint) throws JsonMappingException { }
134 
135                 private void _visit(BeanProperty prop) throws JsonMappingException
136                 {
137                     if (!(prop instanceof BeanPropertyWriter)) {
138                         return;
139                     }
140                     BeanPropertyWriter bpw = (BeanPropertyWriter) prop;
141                     JsonSerializer<?> ser = bpw.getSerializer();
142                     final SerializerProvider prov = getProvider();
143                     if (ser == null) {
144                         if (prov == null) {
145                             throw new Error("SerializerProvider missing");
146                         }
147                         ser = prov.findValueSerializer(prop.getType(), prop);
148                     }
149                     // and this just for bit of extra coverage...
150                     if (ser instanceof StdSerializer) {
151                         assertNotNull(((StdSerializer<?>) ser).getSchema(prov, prop.getType()));
152                     }
153                     JsonFormatVisitorWrapper visitor = new JsonFormatVisitorWrapper.Base(getProvider());
154                     ser.acceptJsonFormatVisitor(visitor, prop.getType());
155                 }
156             };
157         }
158 
159         @Override
expectArrayFormat(JavaType type)160         public JsonArrayFormatVisitor expectArrayFormat(JavaType type) {
161             return new JsonArrayFormatVisitor.Base(getProvider());
162         }
163 
164         @Override
expectStringFormat(JavaType type)165         public JsonStringFormatVisitor expectStringFormat(JavaType type) {
166             return new JsonStringFormatVisitor.Base();
167         }
168 
169         @Override
expectNumberFormat(JavaType type)170         public JsonNumberFormatVisitor expectNumberFormat(JavaType type) {
171             return new JsonNumberFormatVisitor.Base();
172         }
173 
174         @Override
expectIntegerFormat(JavaType type)175         public JsonIntegerFormatVisitor expectIntegerFormat(JavaType type) {
176             return new JsonIntegerFormatVisitor.Base();
177         }
178 
179         @Override
expectBooleanFormat(JavaType type)180         public JsonBooleanFormatVisitor expectBooleanFormat(JavaType type) {
181             return new JsonBooleanFormatVisitor.Base();
182         }
183 
184         @Override
expectNullFormat(JavaType type)185         public JsonNullFormatVisitor expectNullFormat(JavaType type) {
186             return new JsonNullFormatVisitor.Base();
187         }
188 
189         @Override
expectAnyFormat(JavaType type)190         public JsonAnyFormatVisitor expectAnyFormat(JavaType type) {
191             return new JsonAnyFormatVisitor.Base();
192         }
193 
194         @Override
expectMapFormat(JavaType type)195         public JsonMapFormatVisitor expectMapFormat(JavaType type) {
196             return new JsonMapFormatVisitor.Base();
197         }
198     }
199 
200     /*
201     /**********************************************************
202     /* Test methods
203     /**********************************************************
204      */
205 
206     private final ObjectMapper MAPPER = newJsonMapper();
207 
208     /* Silly little test for simply triggering traversal, without attempting to
209      * verify what is being reported. Smoke test that should trigger problems
210      * if basic POJO type/serializer traversal had issues.
211      */
212     public void testBasicTraversal() throws Exception
213     {
214         MAPPER.acceptJsonFormatVisitor(POJO.class, new BogusJsonFormatVisitorWrapper());
215         MAPPER.acceptJsonFormatVisitor(POJOWithScalars.class, new BogusJsonFormatVisitorWrapper());
216         MAPPER.acceptJsonFormatVisitor(LinkedHashMap.class, new BogusJsonFormatVisitorWrapper());
217         MAPPER.acceptJsonFormatVisitor(ArrayList.class, new BogusJsonFormatVisitorWrapper());
218         MAPPER.acceptJsonFormatVisitor(EnumSet.class, new BogusJsonFormatVisitorWrapper());
219 
220         MAPPER.acceptJsonFormatVisitor(POJOWithRefs.class, new BogusJsonFormatVisitorWrapper());
221 
222         MAPPER.acceptJsonFormatVisitor(POJOWithJsonValue.class, new BogusJsonFormatVisitorWrapper());
223     }
224 
225     public void testSimpleEnum() throws Exception
226     {
227         final Set<String> values = new TreeSet<String>();
228         ObjectWriter w = MAPPER.writer(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
229 
230         w.acceptJsonFormatVisitor(TestEnum.class, new JsonFormatVisitorWrapper.Base() {
231             @Override
232             public JsonStringFormatVisitor expectStringFormat(JavaType type) {
233                 return new JsonStringFormatVisitor() {
234                     @Override
235                     public void enumTypes(Set<String> enums) {
236                         values.addAll(enums);
237                     }
238 
239                     @Override
240                     public void format(JsonValueFormat format) { }
241                 };
242             }
243         });
244 
245         assertEquals(3, values.size());
246         TreeSet<String> exp = new TreeSet<String>(Arrays.asList(
247                         "ToString:A",
248                         "ToString:B",
249                         "ToString:C"
250                         ));
251         assertEquals(exp, values);
252     }
253 
254     public void testEnumWithJsonValue() throws Exception
255     {
256         final Set<String> values = new TreeSet<String>();
257         MAPPER.acceptJsonFormatVisitor(TestEnumWithJsonValue.class,
258                 new JsonFormatVisitorWrapper.Base() {
259             @Override
260             public JsonStringFormatVisitor expectStringFormat(JavaType type) {
261                 return new JsonStringFormatVisitor() {
262                     @Override
263                     public void enumTypes(Set<String> enums) {
264                         values.addAll(enums);
265                     }
266 
267                     @Override
268                     public void format(JsonValueFormat format) { }
269                 };
270             }
271         });
272 
273         assertEquals(3, values.size());
274         TreeSet<String> exp = new TreeSet<String>(Arrays.asList(
275                         "value-A",
276                         "value-B",
277                         "value-C"
278                         ));
279         assertEquals(exp, values);
280     }
281 
282     //  Ensure JsonValueFormat serializes/deserializes as expected
283     public void testJsonValueFormatHandling() throws Exception
284     {
285         // first: serialize using 'toString()', not name
286         final String EXP = quote("host-name");
287         assertEquals(EXP, MAPPER.writeValueAsString(JsonValueFormat.HOST_NAME));
288 
289         // and second, deserialize ok from that as well
290         assertSame(JsonValueFormat.HOST_NAME, MAPPER.readValue(EXP, JsonValueFormat.class));
291     }
292 
293     // [databind#1045], regression wrt BigDecimal
294     public void testSimpleNumbers() throws Exception
295     {
296         final StringBuilder sb = new StringBuilder();
297 
298         MAPPER.acceptJsonFormatVisitor(Numbers.class,
299                 new JsonFormatVisitorWrapper.Base() {
300             @Override
301             public JsonObjectFormatVisitor expectObjectFormat(final JavaType type) {
302                 return new JsonObjectFormatVisitor.Base(getProvider()) {
303                     @Override
304                     public void optionalProperty(BeanProperty prop) throws JsonMappingException {
305                         sb.append("[optProp ").append(prop.getName()).append("(");
306                         JsonSerializer<Object> ser = null;
307                         if (prop instanceof BeanPropertyWriter) {
308                             BeanPropertyWriter bpw = (BeanPropertyWriter) prop;
309                             ser = bpw.getSerializer();
310                         }
311                         final SerializerProvider prov = getProvider();
312                         if (ser == null) {
313                             ser = prov.findValueSerializer(prop.getType(), prop);
314                         }
315                         ser.acceptJsonFormatVisitor(new JsonFormatVisitorWrapper.Base() {
316                             @Override
317                             public JsonNumberFormatVisitor expectNumberFormat(
318                                     JavaType t) throws JsonMappingException {
319                                 return new JsonNumberFormatVisitor() {
320                                     @Override
321                                     public void format(JsonValueFormat format) {
322                                         sb.append("[numberFormat=").append(format).append("]");
323                                     }
324 
325                                     @Override
326                                     public void enumTypes(Set<String> enums) { }
327 
328                                     @Override
329                                     public void numberType(NumberType numberType) {
330                                         sb.append("[numberType=").append(numberType).append("]");
331                                     }
332                                 };
333                             }
334 
335                             @Override
336                             public JsonIntegerFormatVisitor expectIntegerFormat(JavaType t) throws JsonMappingException {
337                                 return new JsonIntegerFormatVisitor() {
338                                     @Override
339                                     public void format(JsonValueFormat format) {
340                                         sb.append("[integerFormat=").append(format).append("]");
341                                     }
342 
343                                     @Override
344                                     public void enumTypes(Set<String> enums) { }
345 
346                                     @Override
347                                     public void numberType(NumberType numberType) {
348                                         sb.append("[numberType=").append(numberType).append("]");
349                                     }
350                                 };
351                             }
352                         }, prop.getType());
353 
354                         sb.append(")]");
355                     }
356                 };
357             }
358         });
359         assertEquals("[optProp dec([numberType=BIG_DECIMAL])][optProp bigInt([numberType=BIG_INTEGER])]",
360                 sb.toString());
361     }
362 }
363