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