1
2
3
4
5
6 package dev.metaschema.databind.io.json;
7
8 import com.fasterxml.jackson.core.JsonLocation;
9 import com.fasterxml.jackson.core.JsonParser;
10 import com.fasterxml.jackson.core.JsonToken;
11 import com.fasterxml.jackson.databind.JsonNode;
12 import com.fasterxml.jackson.databind.node.ObjectNode;
13
14 import org.apache.logging.log4j.LogManager;
15 import org.apache.logging.log4j.Logger;
16 import org.eclipse.jdt.annotation.NotOwning;
17
18 import java.io.IOException;
19 import java.net.URI;
20 import java.util.Collection;
21 import java.util.Deque;
22 import java.util.HashMap;
23 import java.util.LinkedHashMap;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Map;
27
28 import dev.metaschema.core.model.IBoundObject;
29 import dev.metaschema.core.model.IResourceLocation;
30 import dev.metaschema.core.model.SimpleResourceLocation;
31 import dev.metaschema.core.model.util.JsonUtil;
32 import dev.metaschema.core.util.ObjectUtils;
33 import dev.metaschema.databind.io.BindingException;
34 import dev.metaschema.databind.io.Format;
35 import dev.metaschema.databind.io.PathTracker;
36 import dev.metaschema.databind.io.ValidationContext;
37 import dev.metaschema.databind.model.IBoundDefinitionModelAssembly;
38 import dev.metaschema.databind.model.IBoundDefinitionModelComplex;
39 import dev.metaschema.databind.model.IBoundDefinitionModelFieldComplex;
40 import dev.metaschema.databind.model.IBoundFieldValue;
41 import dev.metaschema.databind.model.IBoundInstance;
42 import dev.metaschema.databind.model.IBoundInstanceFlag;
43 import dev.metaschema.databind.model.IBoundInstanceModel;
44 import dev.metaschema.databind.model.IBoundInstanceModelAssembly;
45 import dev.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
46 import dev.metaschema.databind.model.IBoundInstanceModelFieldComplex;
47 import dev.metaschema.databind.model.IBoundInstanceModelFieldScalar;
48 import dev.metaschema.databind.model.IBoundInstanceModelGroupedAssembly;
49 import dev.metaschema.databind.model.IBoundInstanceModelGroupedField;
50 import dev.metaschema.databind.model.IBoundInstanceModelGroupedNamed;
51 import dev.metaschema.databind.model.IBoundProperty;
52 import dev.metaschema.databind.model.info.AbstractModelInstanceReadHandler;
53 import dev.metaschema.databind.model.info.IFeatureScalarItemValueHandler;
54 import dev.metaschema.databind.model.info.IItemReadHandler;
55 import dev.metaschema.databind.model.info.IModelInstanceCollectionInfo;
56 import edu.umd.cs.findbugs.annotations.NonNull;
57 import edu.umd.cs.findbugs.annotations.Nullable;
58 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
59
60
61
62
63 @SuppressWarnings({
64 "PMD.CouplingBetweenObjects",
65 "PMD.GodClass"
66 })
67 public class MetaschemaJsonReader
68 implements IJsonParsingContext, IItemReadHandler {
69 private static final Logger LOGGER = LogManager.getLogger(MetaschemaJsonReader.class);
70
71 @NonNull
72 private final Deque<JsonParser> parserStack = new LinkedList<>();
73
74
75 @NonNull
76 private final URI source;
77 @NonNull
78 private final IJsonProblemHandler problemHandler;
79
80
81
82 @NonNull
83 private final PathTracker pathTracker = new PathTracker();
84
85
86
87
88
89
90
91
92
93
94
95
96 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
97 public MetaschemaJsonReader(
98 @NonNull JsonParser parser,
99 @NonNull URI source) throws IOException {
100 this(parser, source, new DefaultJsonProblemHandler());
101 }
102
103
104
105
106
107
108
109
110
111
112
113
114
115 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
116 public MetaschemaJsonReader(
117 @NonNull JsonParser parser,
118 @NonNull URI source,
119 @NonNull IJsonProblemHandler problemHandler) throws IOException {
120 this.source = source;
121 this.problemHandler = problemHandler;
122 push(parser);
123 }
124
125 @SuppressWarnings("resource")
126 @NotOwning
127 @Override
128 public JsonParser getReader() {
129 return ObjectUtils.notNull(parserStack.peek());
130 }
131
132 @Override
133 public URI getSource() {
134 return source;
135 }
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 @SuppressWarnings("resource")
160 private void push(@NonNull JsonParser parser) throws IOException {
161 assert !parser.equals(parserStack.peek());
162 if (parser.getCurrentToken() == null) {
163 parser.nextToken();
164 }
165 parserStack.push(parser);
166 }
167
168 @SuppressWarnings({ "resource", "PMD.CloseResource" })
169 @NonNull
170 private JsonParser pop(@NonNull JsonParser parser) {
171 JsonParser old = parserStack.pop();
172 assert parser.equals(old);
173 return ObjectUtils.notNull(parserStack.peek());
174 }
175
176 @Override
177 public IJsonProblemHandler getProblemHandler() {
178 return problemHandler;
179 }
180
181
182
183
184
185
186 @NonNull
187 private ValidationContext buildValidationContext() {
188 JsonParser parser = getReader();
189 JsonLocation location = parser.currentLocation();
190 IResourceLocation resourceLocation = JsonLocation.NA.equals(location)
191 ? SimpleResourceLocation.UNKNOWN
192 : SimpleResourceLocation.fromJsonLocation(location);
193 return ValidationContext.of(source, resourceLocation, pathTracker.getCurrentPath(), Format.JSON);
194 }
195
196
197
198
199
200
201
202
203
204
205
206
207 @SuppressWarnings("unchecked")
208 @NonNull
209 public <T> T readObject(@NonNull IBoundDefinitionModelComplex definition) throws IOException {
210 T value = (T) definition.readItem(null, this);
211 if (value == null) {
212 throw new IOException(String.format("Failed to read object '%s'%s.",
213 definition.getDefinitionQName(),
214 JsonUtil.generateLocationMessage(getReader(), getSource())));
215 }
216 return value;
217 }
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232 @SuppressWarnings({
233 "unchecked",
234 "PMD.CyclomaticComplexity"
235 })
236 @NonNull
237 public <T> T readObjectRoot(
238 @NonNull IBoundDefinitionModelComplex definition,
239 @NonNull String expectedFieldName) throws IOException {
240 @SuppressWarnings("PMD.CloseResource")
241 JsonParser parser = getReader();
242 URI resource = getSource();
243
244 boolean hasStartObject = JsonToken.START_OBJECT.equals(parser.currentToken());
245 if (hasStartObject) {
246
247 JsonUtil.assertAndAdvance(parser, resource, JsonToken.START_OBJECT);
248 }
249
250 T retval = null;
251 JsonToken token;
252 while (!JsonToken.END_OBJECT.equals(token = parser.currentToken()) && token != null) {
253 if (!JsonToken.FIELD_NAME.equals(token)) {
254 throw new IOException(String.format("Expected FIELD_NAME token, found '%s'", token.toString()));
255 }
256
257 String propertyName = ObjectUtils.notNull(parser.currentName());
258 if (expectedFieldName.equals(propertyName)) {
259
260 JsonUtil.assertAndAdvance(parser, resource, JsonToken.FIELD_NAME);
261
262
263 retval = (T) definition.readItem(null, this);
264 break;
265 }
266
267 if (!getProblemHandler().handleUnknownProperty(
268 definition,
269 null,
270 propertyName,
271 this)) {
272 if (LOGGER.isWarnEnabled()) {
273 LOGGER.warn("Skipping unhandled JSON field '{}'{}.", propertyName, JsonUtil.toString(parser, resource));
274 }
275 JsonUtil.skipNextValue(parser, resource);
276 }
277 }
278
279 if (hasStartObject) {
280
281 JsonUtil.assertAndAdvance(parser, resource, JsonToken.END_OBJECT);
282 }
283
284 if (retval == null) {
285 throw new IOException(String.format("Failed to find property with name '%s'%s.",
286 expectedFieldName,
287 JsonUtil.generateLocationMessage(parser, resource)));
288 }
289 return retval;
290 }
291
292
293
294
295
296 @Nullable
297 private Object readInstance(
298 @NonNull IBoundProperty<?> instance,
299 @NonNull IBoundObject parent) throws IOException {
300 return instance.readItem(parent, this);
301 }
302
303 @Nullable
304 private <T> Object readModelInstance(
305 @NonNull IBoundInstanceModel<T> instance,
306 @NonNull IBoundObject parent) throws IOException {
307 IModelInstanceCollectionInfo<T> collectionInfo = instance.getCollectionInfo();
308 return collectionInfo.readItems(new ModelInstanceReadHandler<>(instance, parent));
309 }
310
311 private Object readFieldValue(
312 @NonNull IBoundFieldValue instance,
313 @NonNull IBoundObject parent) throws IOException {
314
315 return instance.readItem(parent, this);
316 }
317
318 @Nullable
319 private Object readObjectProperty(
320 @NonNull IBoundObject parent,
321 @NonNull IBoundProperty<?> property) throws IOException {
322 Object retval;
323 if (property instanceof IBoundInstanceModel) {
324 retval = readModelInstance((IBoundInstanceModel<?>) property, parent);
325 } else if (property instanceof IBoundInstance) {
326 retval = readInstance(property, parent);
327 } else {
328 retval = readFieldValue((IBoundFieldValue) property, parent);
329 }
330 return retval;
331 }
332
333 @Override
334 public Object readItemFlag(IBoundObject parentItem, IBoundInstanceFlag instance) throws IOException {
335 return readScalarItem(instance);
336 }
337
338 @Override
339 public Object readItemField(IBoundObject parentItem, IBoundInstanceModelFieldScalar instance) throws IOException {
340 return readScalarItem(instance);
341 }
342
343 @Override
344 public IBoundObject readItemField(IBoundObject parentItem, IBoundInstanceModelFieldComplex instance)
345 throws IOException {
346 return readFieldObject(
347 parentItem,
348 instance.getDefinition(),
349 instance.getJsonProperties(),
350 instance.getEffectiveJsonKey(),
351 getProblemHandler());
352 }
353
354 @Override
355 public IBoundObject readItemField(IBoundObject parentItem, IBoundInstanceModelGroupedField instance)
356 throws IOException {
357 IJsonProblemHandler problemHandler = new GroupedInstanceProblemHandler(instance, getProblemHandler());
358 IBoundDefinitionModelFieldComplex definition = instance.getDefinition();
359 IBoundInstanceFlag jsonValueKeyFlag = definition.getJsonValueKeyFlagInstance();
360
361 IJsonProblemHandler actualProblemHandler = jsonValueKeyFlag == null
362 ? problemHandler
363 : new JsomValueKeyProblemHandler(problemHandler, jsonValueKeyFlag);
364
365 return readComplexDefinitionObject(
366 parentItem,
367 definition,
368 instance.getEffectiveJsonKey(),
369 new PropertyBodyHandler(instance.getJsonProperties()),
370 actualProblemHandler);
371 }
372
373 @Override
374 public IBoundObject readItemField(IBoundObject parentItem, IBoundDefinitionModelFieldComplex definition)
375 throws IOException {
376 return readFieldObject(
377 parentItem,
378 definition,
379 definition.getJsonProperties(),
380 null,
381 getProblemHandler());
382 }
383
384 @Override
385 public Object readItemFieldValue(IBoundObject parentItem, IBoundFieldValue fieldValue) throws IOException {
386
387 return checkMissingFieldValue(readScalarItem(fieldValue));
388 }
389
390 @Nullable
391 private Object checkMissingFieldValue(Object value) {
392 if (value == null && LOGGER.isWarnEnabled()) {
393 LOGGER.atWarn().log("Missing property value{}",
394 JsonUtil.generateLocationMessage(getReader(), getSource()));
395 }
396 return value;
397 }
398
399 @Override
400 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundInstanceModelAssembly instance)
401 throws IOException {
402 IBoundInstanceFlag jsonKey = instance.getJsonKey();
403 IBoundDefinitionModelComplex definition = instance.getDefinition();
404 return readComplexDefinitionObject(
405 parentItem,
406 definition,
407 jsonKey,
408 new PropertyBodyHandler(instance.getJsonProperties()),
409 getProblemHandler());
410 }
411
412 @Override
413 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundInstanceModelGroupedAssembly instance)
414 throws IOException {
415 return readComplexDefinitionObject(
416 parentItem,
417 instance.getDefinition(),
418 instance.getEffectiveJsonKey(),
419 new PropertyBodyHandler(instance.getJsonProperties()),
420 new GroupedInstanceProblemHandler(instance, getProblemHandler()));
421 }
422
423 @Override
424 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundDefinitionModelAssembly definition)
425 throws IOException {
426 return readComplexDefinitionObject(
427 parentItem,
428 definition,
429 null,
430 new PropertyBodyHandler(definition.getJsonProperties()),
431 getProblemHandler());
432 }
433
434 @NonNull
435 private Object readScalarItem(@NonNull IFeatureScalarItemValueHandler handler)
436 throws IOException {
437 return handler.getJavaTypeAdapter().parse(getReader(), getSource());
438 }
439
440 @NonNull
441 private IBoundObject readFieldObject(
442 @Nullable IBoundObject parentItem,
443 @NonNull IBoundDefinitionModelFieldComplex definition,
444 @NonNull Map<String, IBoundProperty<?>> jsonProperties,
445 @Nullable IBoundInstanceFlag jsonKey,
446 @NonNull IJsonProblemHandler problemHandler) throws IOException {
447 IBoundInstanceFlag jsonValueKey = definition.getJsonValueKeyFlagInstance();
448 IJsonProblemHandler actualProblemHandler = jsonValueKey == null
449 ? problemHandler
450 : new JsomValueKeyProblemHandler(problemHandler, jsonValueKey);
451
452 IBoundObject retval;
453 if (jsonProperties.isEmpty() && jsonValueKey == null) {
454 retval = readComplexDefinitionObject(
455 parentItem,
456 definition,
457 jsonKey,
458 (def, parent, problem) -> {
459 IBoundFieldValue fieldValue = definition.getFieldValue();
460 Object item = readItemFieldValue(parent, fieldValue);
461 if (item != null) {
462 fieldValue.setValue(parent, item);
463 }
464 },
465 actualProblemHandler);
466
467 } else {
468 retval = readComplexDefinitionObject(
469 parentItem,
470 definition,
471 jsonKey,
472 new PropertyBodyHandler(jsonProperties),
473 actualProblemHandler);
474 }
475 return retval;
476 }
477
478 @NonNull
479 private IBoundObject readComplexDefinitionObject(
480 @Nullable IBoundObject parentItem,
481 @NonNull IBoundDefinitionModelComplex definition,
482 @Nullable IBoundInstanceFlag jsonKey,
483 @NonNull DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler,
484 @NonNull IJsonProblemHandler problemHandler) throws IOException {
485 DefinitionBodyHandler<IBoundDefinitionModelComplex> actualBodyHandler = jsonKey == null
486 ? bodyHandler
487 : new JsonKeyBodyHandler(jsonKey, bodyHandler);
488
489 JsonLocation location = getReader().currentLocation();
490
491
492 IBoundObject item = definition.newInstance(
493 JsonLocation.NA.equals(location)
494 ? null
495 : () -> SimpleResourceLocation.fromJsonLocation(ObjectUtils.requireNonNull(location)));
496
497 try {
498
499 definition.callBeforeDeserialize(item, parentItem);
500
501
502 actualBodyHandler.accept(definition, item, problemHandler);
503
504
505 definition.callAfterDeserialize(item, parentItem);
506 } catch (BindingException ex) {
507 throw new IOException(ex);
508 }
509
510 return item;
511 }
512
513 @SuppressWarnings("resource")
514 @Override
515 public IBoundObject readChoiceGroupItem(IBoundObject parentItem, IBoundInstanceModelChoiceGroup instance)
516 throws IOException {
517 @SuppressWarnings("PMD.CloseResource")
518 JsonParser parser = getReader();
519 ObjectNode node = parser.readValueAsTree();
520
521 String discriminatorProperty = instance.getJsonDiscriminatorProperty();
522 JsonNode discriminatorNode = node.get(discriminatorProperty);
523 if (discriminatorNode == null) {
524 throw new IllegalArgumentException(String.format(
525 "Unable to find discriminator property '%s' for object at '%s'.",
526 discriminatorProperty,
527 JsonUtil.toString(parser, getSource())));
528 }
529 String discriminator = ObjectUtils.requireNonNull(discriminatorNode.asText());
530
531 IBoundInstanceModelGroupedNamed actualInstance = instance.getGroupedModelInstance(discriminator);
532 assert actualInstance != null;
533
534 IBoundObject retval;
535 try (JsonParser newParser = node.traverse(parser.getCodec())) {
536 assert newParser != null;
537 push(newParser);
538
539
540 retval = actualInstance.readItem(parentItem, this);
541 assert newParser.currentToken() == null;
542 pop(newParser);
543 }
544
545
546 parser.nextToken();
547
548 return retval;
549 }
550
551 private final class JsonKeyBodyHandler implements DefinitionBodyHandler<IBoundDefinitionModelComplex> {
552 @NonNull
553 private final IBoundInstanceFlag jsonKey;
554 @NonNull
555 private final DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler;
556
557 private JsonKeyBodyHandler(
558 @NonNull IBoundInstanceFlag jsonKey,
559 @NonNull DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler) {
560 this.jsonKey = jsonKey;
561 this.bodyHandler = bodyHandler;
562 }
563
564 @Override
565 public void accept(
566 IBoundDefinitionModelComplex definition,
567 IBoundObject parent,
568 IJsonProblemHandler problemHandler)
569 throws IOException {
570 @SuppressWarnings("PMD.CloseResource")
571 JsonParser parser = getReader();
572 URI resource = getSource();
573 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME);
574
575
576 String key = ObjectUtils.notNull(parser.currentName());
577 try {
578 Object value = jsonKey.getDefinition().getJavaTypeAdapter().parse(key);
579 jsonKey.setValue(parent, ObjectUtils.notNull(value.toString()));
580 } catch (IllegalArgumentException ex) {
581 throw new IOException(
582 String.format("Malformed data '%s'%s. %s",
583 key,
584 JsonUtil.generateLocationMessage(parser, resource),
585 ex.getLocalizedMessage()),
586 ex);
587 }
588
589
590 parser.nextToken();
591
592
593
594
595
596
597 bodyHandler.accept(definition, parent, problemHandler);
598
599
600
601 }
602 }
603
604 private final class PropertyBodyHandler implements DefinitionBodyHandler<IBoundDefinitionModelComplex> {
605 @NonNull
606 private final Map<String, IBoundProperty<?>> jsonProperties;
607
608 private PropertyBodyHandler(@NonNull Map<String, IBoundProperty<?>> jsonProperties) {
609 this.jsonProperties = jsonProperties;
610 }
611
612 @Override
613 public void accept(
614 IBoundDefinitionModelComplex definition,
615 IBoundObject parent,
616 IJsonProblemHandler problemHandler)
617 throws IOException {
618 @SuppressWarnings("PMD.CloseResource")
619 JsonParser parser = getReader();
620 URI resource = getSource();
621
622
623 pathTracker.push(definition.getEffectiveName());
624
625 try {
626
627 JsonUtil.assertAndAdvance(parser, resource, JsonToken.START_OBJECT);
628
629
630 Map<String, IBoundProperty<?>> remainingInstances = new HashMap<>(jsonProperties);
631
632
633 while (JsonToken.FIELD_NAME.equals(parser.currentToken())) {
634
635
636 String propertyName = ObjectUtils.notNull(parser.currentName());
637 if (LOGGER.isTraceEnabled()) {
638 LOGGER.trace("reading property {}", propertyName);
639 }
640
641 IBoundProperty<?> property = remainingInstances.get(propertyName);
642
643 boolean handled = false;
644 if (property != null) {
645
646 parser.nextToken();
647
648 Object value = readObjectProperty(parent, property);
649 if (value != null) {
650 property.setValue(parent, value);
651 }
652
653
654 remainingInstances.remove(propertyName);
655 handled = true;
656 }
657
658 if (!handled && !problemHandler.handleUnknownProperty(
659 definition,
660 parent,
661 propertyName,
662 MetaschemaJsonReader.this)) {
663 if (LOGGER.isWarnEnabled()) {
664 LOGGER.warn("Skipping unhandled JSON field '{}' {}.", propertyName, JsonUtil.toString(parser, resource));
665 }
666 JsonUtil.assertAndAdvance(parser, resource, JsonToken.FIELD_NAME);
667 JsonUtil.skipNextValue(parser, resource);
668 }
669
670
671
672 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME, JsonToken.END_OBJECT);
673 }
674
675
676 ValidationContext context = buildValidationContext();
677 problemHandler.handleMissingInstances(
678 definition,
679 parent,
680 ObjectUtils.notNull(remainingInstances.values()),
681 context);
682
683
684 JsonUtil.assertAndAdvance(parser, resource, JsonToken.END_OBJECT);
685 } finally {
686 pathTracker.pop();
687 }
688 }
689 }
690
691 private static final class GroupedInstanceProblemHandler implements IJsonProblemHandler {
692 @NonNull
693 private final IBoundInstanceModelGroupedNamed instance;
694 @NonNull
695 private final IJsonProblemHandler delegate;
696
697 private GroupedInstanceProblemHandler(
698 @NonNull IBoundInstanceModelGroupedNamed instance,
699 @NonNull IJsonProblemHandler delegate) {
700 this.instance = instance;
701 this.delegate = delegate;
702 }
703
704 @Override
705 public void handleMissingInstances(
706 IBoundDefinitionModelComplex parentDefinition,
707 IBoundObject targetObject,
708 Collection<? extends IBoundProperty<?>> unhandledInstances) throws IOException {
709 delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances);
710 }
711
712 @Override
713 public void handleMissingInstances(
714 IBoundDefinitionModelComplex parentDefinition,
715 IBoundObject targetObject,
716 Collection<? extends IBoundProperty<?>> unhandledInstances,
717 ValidationContext context) throws IOException {
718 delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances, context);
719 }
720
721 @Override
722 public boolean handleUnknownProperty(
723 IBoundDefinitionModelComplex definition,
724 IBoundObject parentItem,
725 String fieldName,
726 IJsonParsingContext parsingContext) throws IOException {
727 boolean retval;
728 if (instance.getParentContainer().getJsonDiscriminatorProperty().equals(fieldName)) {
729 JsonUtil.skipNextValue(parsingContext.getReader(), parsingContext.getSource());
730 retval = true;
731 } else {
732 retval = delegate.handleUnknownProperty(definition, parentItem, fieldName, parsingContext);
733 }
734 return retval;
735 }
736 }
737
738 private final class JsomValueKeyProblemHandler implements IJsonProblemHandler {
739 @NonNull
740 private final IJsonProblemHandler delegate;
741 @NonNull
742 private final IBoundInstanceFlag jsonValueKeyFlag;
743 private boolean foundJsonValueKey;
744
745 private JsomValueKeyProblemHandler(
746 @NonNull IJsonProblemHandler delegate,
747 @NonNull IBoundInstanceFlag jsonValueKeyFlag) {
748 this.delegate = delegate;
749 this.jsonValueKeyFlag = jsonValueKeyFlag;
750 }
751
752 @Override
753 public void handleMissingInstances(
754 IBoundDefinitionModelComplex parentDefinition,
755 IBoundObject targetObject,
756 Collection<? extends IBoundProperty<?>> unhandledInstances) throws IOException {
757 delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances);
758 }
759
760 @Override
761 public void handleMissingInstances(
762 IBoundDefinitionModelComplex parentDefinition,
763 IBoundObject targetObject,
764 Collection<? extends IBoundProperty<?>> unhandledInstances,
765 ValidationContext context) throws IOException {
766 delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances, context);
767 }
768
769 @Override
770 public boolean handleUnknownProperty(
771 IBoundDefinitionModelComplex definition,
772 IBoundObject parentItem,
773 String fieldName,
774 IJsonParsingContext parsingContext) throws IOException {
775 boolean retval;
776 if (foundJsonValueKey) {
777 retval = delegate.handleUnknownProperty(definition, parentItem, fieldName, parsingContext);
778 } else {
779 @SuppressWarnings("PMD.CloseResource")
780 JsonParser parser = parsingContext.getReader();
781 URI resource = parsingContext.getSource();
782
783
784 String key = ObjectUtils.notNull(parser.currentName());
785 try {
786 Object keyValue = jsonValueKeyFlag.getJavaTypeAdapter().parse(key);
787 jsonValueKeyFlag.setValue(ObjectUtils.notNull(parentItem), keyValue);
788 } catch (IllegalArgumentException ex) {
789 throw new IOException(
790 String.format("Malformed data '%s'%s. %s",
791 key,
792 JsonUtil.generateLocationMessage(parser, resource),
793 ex.getLocalizedMessage()),
794 ex);
795 }
796
797 JsonUtil.assertAndAdvance(parser, resource, JsonToken.FIELD_NAME);
798
799 IBoundFieldValue fieldValue = ((IBoundDefinitionModelFieldComplex) definition).getFieldValue();
800 Object value = readItemFieldValue(ObjectUtils.notNull(parentItem), fieldValue);
801 if (value != null) {
802 fieldValue.setValue(ObjectUtils.notNull(parentItem), value);
803 }
804
805 retval = foundJsonValueKey = true;
806 }
807 return retval;
808 }
809 }
810
811 private class ModelInstanceReadHandler<ITEM>
812 extends AbstractModelInstanceReadHandler<ITEM> {
813
814 protected ModelInstanceReadHandler(
815 @NonNull IBoundInstanceModel<ITEM> instance,
816 @NonNull IBoundObject parentItem) {
817 super(instance, parentItem);
818 }
819
820 @Override
821 public List<ITEM> readList() throws IOException {
822 @SuppressWarnings("PMD.CloseResource")
823 JsonParser parser = getReader();
824 URI resource = getSource();
825
826 List<ITEM> items = new LinkedList<>();
827 switch (parser.currentToken()) {
828 case START_ARRAY:
829
830 JsonUtil.assertAndAdvance(parser, resource, JsonToken.START_ARRAY);
831
832
833 while (!JsonToken.END_ARRAY.equals(parser.currentToken())) {
834 items.add(readItem());
835 }
836
837
838 JsonUtil.assertAndAdvance(parser, resource, JsonToken.END_ARRAY);
839 break;
840 case VALUE_NULL:
841 JsonUtil.assertAndAdvance(parser, resource, JsonToken.VALUE_NULL);
842 break;
843 default:
844
845 items.add(readItem());
846 break;
847 }
848 return items;
849 }
850
851 @Override
852 public Map<String, ITEM> readMap() throws IOException {
853 @SuppressWarnings("PMD.CloseResource")
854 JsonParser parser = getReader();
855 URI resource = getSource();
856
857 IBoundInstanceModel<?> instance = getCollectionInfo().getInstance();
858
859 @SuppressWarnings("PMD.UseConcurrentHashMap")
860 Map<String, ITEM> items = new LinkedHashMap<>();
861
862
863
864 JsonUtil.assertAndAdvance(parser, resource, JsonToken.START_OBJECT);
865
866
867 while (!JsonToken.END_OBJECT.equals(parser.currentToken())) {
868
869
870 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME);
871
872
873 ITEM item = readItem();
874 if (item == null) {
875 throw new IOException(String.format("Null object encountered'%s.",
876 JsonUtil.generateLocationMessage(parser, resource)));
877 }
878
879
880 IBoundInstanceFlag jsonKey = instance.getItemJsonKey(item);
881 assert jsonKey != null;
882
883 Object keyValue = jsonKey.getValue(item);
884 if (keyValue == null) {
885 throw new IOException(String.format("Null value for json-key for definition '%s'",
886 jsonKey.getContainingDefinition().toCoordinates()));
887 }
888 String key;
889 try {
890 key = jsonKey.getJavaTypeAdapter().asString(keyValue);
891 } catch (IllegalArgumentException ex) {
892 throw new IOException(
893 String.format("Malformed data '%s'%s. %s",
894 keyValue,
895 JsonUtil.generateLocationMessage(parser, resource),
896 ex.getLocalizedMessage()),
897 ex);
898 }
899 items.put(key, item);
900
901
902
903
904 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME, JsonToken.END_OBJECT);
905 }
906
907
908 JsonUtil.assertAndAdvance(parser, resource, JsonToken.END_OBJECT);
909
910 return items;
911 }
912
913 @Override
914 public ITEM readItem() throws IOException {
915 IBoundInstanceModel<ITEM> instance = getCollectionInfo().getInstance();
916 return instance.readItem(getParentObject(), MetaschemaJsonReader.this);
917 }
918 }
919
920 @FunctionalInterface
921 private interface DefinitionBodyHandler<DEF extends IBoundDefinitionModelComplex> {
922 void accept(
923 @NonNull DEF definition,
924 @NonNull IBoundObject parent,
925 @NonNull IJsonProblemHandler problemHandler) throws IOException;
926 }
927 }