001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.databind.io.json; 007 008import com.fasterxml.jackson.core.JsonLocation; 009import com.fasterxml.jackson.core.JsonParser; 010import com.fasterxml.jackson.core.JsonToken; 011import com.fasterxml.jackson.databind.JsonNode; 012import com.fasterxml.jackson.databind.node.ObjectNode; 013 014import gov.nist.secauto.metaschema.core.model.IBoundObject; 015import gov.nist.secauto.metaschema.core.model.IMetaschemaData; 016import gov.nist.secauto.metaschema.core.model.util.JsonUtil; 017import gov.nist.secauto.metaschema.core.util.ObjectUtils; 018import gov.nist.secauto.metaschema.databind.io.BindingException; 019import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly; 020import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex; 021import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelFieldComplex; 022import gov.nist.secauto.metaschema.databind.model.IBoundFieldValue; 023import gov.nist.secauto.metaschema.databind.model.IBoundInstance; 024import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag; 025import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel; 026import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly; 027import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup; 028import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldComplex; 029import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldScalar; 030import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedAssembly; 031import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedField; 032import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedNamed; 033import gov.nist.secauto.metaschema.databind.model.IBoundProperty; 034import gov.nist.secauto.metaschema.databind.model.info.AbstractModelInstanceReadHandler; 035import gov.nist.secauto.metaschema.databind.model.info.IFeatureScalarItemValueHandler; 036import gov.nist.secauto.metaschema.databind.model.info.IItemReadHandler; 037import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo; 038 039import org.apache.logging.log4j.LogManager; 040import org.apache.logging.log4j.Logger; 041import org.eclipse.jdt.annotation.NotOwning; 042 043import java.io.IOException; 044import java.net.URI; 045import java.util.Collection; 046import java.util.Deque; 047import java.util.HashMap; 048import java.util.LinkedHashMap; 049import java.util.LinkedList; 050import java.util.List; 051import java.util.Map; 052 053import edu.umd.cs.findbugs.annotations.NonNull; 054import edu.umd.cs.findbugs.annotations.Nullable; 055import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 056 057/** 058 * Supports reading JSON-based Metaschema module instances. 059 */ 060@SuppressWarnings({ 061 "PMD.CouplingBetweenObjects", 062 "PMD.GodClass" 063}) 064public class MetaschemaJsonReader 065 implements IJsonParsingContext, IItemReadHandler { 066 private static final Logger LOGGER = LogManager.getLogger(MetaschemaJsonReader.class); 067 068 @NonNull 069 private final Deque<JsonParser> parserStack = new LinkedList<>(); 070 // @NonNull 071 // private final InstanceReader instanceReader = new InstanceReader(); 072 @NonNull 073 private final URI source; 074 @NonNull 075 private final IJsonProblemHandler problemHandler; 076 077 /** 078 * Construct a new Module-aware JSON parser using the default problem handler. 079 * 080 * @param parser 081 * the JSON parser to parse with 082 * @param source 083 * the resource being parsed 084 * @throws IOException 085 * if an error occurred while reading the JSON 086 * @see DefaultJsonProblemHandler 087 */ 088 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields") 089 public MetaschemaJsonReader( 090 @NonNull JsonParser parser, 091 @NonNull URI source) throws IOException { 092 this(parser, source, new DefaultJsonProblemHandler()); 093 } 094 095 /** 096 * Construct a new Module-aware JSON parser. 097 * 098 * @param parser 099 * the JSON parser to parse with 100 * @param source 101 * the resource being parsed 102 * @param problemHandler 103 * the problem handler implementation to use 104 * @throws IOException 105 * if an error occurred while reading the JSON 106 */ 107 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields") 108 public MetaschemaJsonReader( 109 @NonNull JsonParser parser, 110 @NonNull URI source, 111 @NonNull IJsonProblemHandler problemHandler) throws IOException { 112 this.source = source; 113 this.problemHandler = problemHandler; 114 push(parser); 115 } 116 117 @SuppressWarnings("resource") 118 @NotOwning 119 @Override 120 public JsonParser getReader() { 121 return ObjectUtils.notNull(parserStack.peek()); 122 } 123 124 @Override 125 public URI getSource() { 126 return source; 127 } 128 // protected void analyzeParserStack(@NonNull String action) throws IOException 129 // { 130 // StringBuilder builder = new StringBuilder() 131 // .append("------\n"); 132 // 133 // for (JsonParser parser : parserStack) { 134 // JsonToken token = parser.getCurrentToken(); 135 // if (token == null) { 136 // LOGGER.info(String.format("Advancing parser: %s", parser.hashCode())); 137 // token = parser.nextToken(); 138 // } 139 // 140 // String name = parser.currentName(); 141 // builder.append(String.format("%s: %d: %s(%s)%s\n", 142 // action, 143 // parser.hashCode(), 144 // token.name(), 145 // name == null ? "" : name, 146 // JsonUtil.generateLocationMessage(parser))); 147 // } 148 // LOGGER.info(builder.toString()); 149 // } 150 151 @SuppressWarnings({ "resource", "PMD.CloseResource" }) 152 private void push(@NonNull JsonParser parser) throws IOException { 153 assert !parser.equals(parserStack.peek()); 154 if (parser.getCurrentToken() == null) { 155 parser.nextToken(); 156 } 157 parserStack.push(parser); 158 } 159 160 @SuppressWarnings({ "resource", "PMD.CloseResource" }) 161 @NonNull 162 private JsonParser pop(@NonNull JsonParser parser) { 163 JsonParser old = parserStack.pop(); 164 assert parser.equals(old); 165 return ObjectUtils.notNull(parserStack.peek()); 166 } 167 168 @Override 169 public IJsonProblemHandler getProblemHandler() { 170 return problemHandler; 171 } 172 173 /** 174 * Read a JSON object value based on the provided definition. 175 * 176 * @param <T> 177 * the Java type of the bound object produced by this parser 178 * @param definition 179 * the Metaschema module definition that describes the node to parse 180 * @return the resulting parsed bound object 181 * @throws IOException 182 * if an error occurred while parsing the content 183 */ 184 @SuppressWarnings("unchecked") 185 @NonNull 186 public <T> T readObject(@NonNull IBoundDefinitionModelComplex definition) throws IOException { 187 T value = (T) definition.readItem(null, this); 188 if (value == null) { 189 throw new IOException(String.format("Failed to read object '%s'%s.", 190 definition.getDefinitionQName(), 191 JsonUtil.generateLocationMessage(getReader(), getSource()))); 192 } 193 return value; 194 } 195 196 /** 197 * Read a JSON property based on the provided definition. 198 * 199 * @param <T> 200 * the Java type of the bound object produced by this parser 201 * @param definition 202 * the Metaschema module definition that describes the node to parse 203 * @param expectedFieldName 204 * the name of the JSON field to parse 205 * @return the resulting parsed bound object 206 * @throws IOException 207 * if an error occurred while parsing the content 208 */ 209 @SuppressWarnings({ 210 "unchecked", 211 "PMD.CyclomaticComplexity" 212 }) 213 @NonNull 214 public <T> T readObjectRoot( 215 @NonNull IBoundDefinitionModelComplex definition, 216 @NonNull String expectedFieldName) throws IOException { 217 @SuppressWarnings("PMD.CloseResource") 218 JsonParser parser = getReader(); 219 URI resource = getSource(); 220 221 boolean hasStartObject = JsonToken.START_OBJECT.equals(parser.currentToken()); 222 if (hasStartObject) { 223 // advance past the start object 224 JsonUtil.assertAndAdvance(parser, resource, JsonToken.START_OBJECT); 225 } 226 227 T retval = null; 228 JsonToken token; 229 while (!JsonToken.END_OBJECT.equals(token = parser.currentToken()) && token != null) { 230 if (!JsonToken.FIELD_NAME.equals(token)) { 231 throw new IOException(String.format("Expected FIELD_NAME token, found '%s'", token.toString())); 232 } 233 234 String propertyName = ObjectUtils.notNull(parser.currentName()); 235 if (expectedFieldName.equals(propertyName)) { 236 // process the object value, bound to the requested class 237 JsonUtil.assertAndAdvance(parser, resource, JsonToken.FIELD_NAME); 238 239 // stop now, since we found the field 240 retval = (T) definition.readItem(null, this); 241 break; 242 } 243 244 if (!getProblemHandler().handleUnknownProperty( 245 definition, 246 null, 247 propertyName, 248 this)) { 249 if (LOGGER.isWarnEnabled()) { 250 LOGGER.warn("Skipping unhandled JSON field '{}'{}.", propertyName, JsonUtil.toString(parser, resource)); 251 } 252 JsonUtil.skipNextValue(parser, resource); 253 } 254 } 255 256 if (hasStartObject) { 257 // advance past the end object 258 JsonUtil.assertAndAdvance(parser, resource, JsonToken.END_OBJECT); 259 } 260 261 if (retval == null) { 262 throw new IOException(String.format("Failed to find property with name '%s'%s.", 263 expectedFieldName, 264 JsonUtil.generateLocationMessage(parser, resource))); 265 } 266 return retval; 267 } 268 269 // ================ 270 // Instance readers 271 // ================ 272 273 @Nullable 274 private Object readInstance( 275 @NonNull IBoundProperty<?> instance, 276 @NonNull IBoundObject parent) throws IOException { 277 return instance.readItem(parent, this); 278 } 279 280 @Nullable 281 private <T> Object readModelInstance( 282 @NonNull IBoundInstanceModel<T> instance, 283 @NonNull IBoundObject parent) throws IOException { 284 IModelInstanceCollectionInfo<T> collectionInfo = instance.getCollectionInfo(); 285 return collectionInfo.readItems(new ModelInstanceReadHandler<>(instance, parent)); 286 } 287 288 private Object readFieldValue( 289 @NonNull IBoundFieldValue instance, 290 @NonNull IBoundObject parent) throws IOException { 291 // handle the value key name case 292 return instance.readItem(parent, this); 293 } 294 295 @Nullable 296 private Object readObjectProperty( 297 @NonNull IBoundObject parent, 298 @NonNull IBoundProperty<?> property) throws IOException { 299 Object retval; 300 if (property instanceof IBoundInstanceModel) { 301 retval = readModelInstance((IBoundInstanceModel<?>) property, parent); 302 } else if (property instanceof IBoundInstance) { 303 retval = readInstance(property, parent); 304 } else { // IBoundFieldValue 305 retval = readFieldValue((IBoundFieldValue) property, parent); 306 } 307 return retval; 308 } 309 310 @Override 311 public Object readItemFlag(IBoundObject parentItem, IBoundInstanceFlag instance) throws IOException { 312 return readScalarItem(instance); 313 } 314 315 @Override 316 public Object readItemField(IBoundObject parentItem, IBoundInstanceModelFieldScalar instance) throws IOException { 317 return readScalarItem(instance); 318 } 319 320 @Override 321 public IBoundObject readItemField(IBoundObject parentItem, IBoundInstanceModelFieldComplex instance) 322 throws IOException { 323 return readFieldObject( 324 parentItem, 325 instance.getDefinition(), 326 instance.getJsonProperties(), 327 instance.getEffectiveJsonKey(), 328 getProblemHandler()); 329 } 330 331 @Override 332 public IBoundObject readItemField(IBoundObject parentItem, IBoundInstanceModelGroupedField instance) 333 throws IOException { 334 IJsonProblemHandler problemHandler = new GroupedInstanceProblemHandler(instance, getProblemHandler()); 335 IBoundDefinitionModelFieldComplex definition = instance.getDefinition(); 336 IBoundInstanceFlag jsonValueKeyFlag = definition.getJsonValueKeyFlagInstance(); 337 338 IJsonProblemHandler actualProblemHandler = jsonValueKeyFlag == null 339 ? problemHandler 340 : new JsomValueKeyProblemHandler(problemHandler, jsonValueKeyFlag); 341 342 return readComplexDefinitionObject( 343 parentItem, 344 definition, 345 instance.getEffectiveJsonKey(), 346 new PropertyBodyHandler(instance.getJsonProperties()), 347 actualProblemHandler); 348 } 349 350 @Override 351 public IBoundObject readItemField(IBoundObject parentItem, IBoundDefinitionModelFieldComplex definition) 352 throws IOException { 353 return readFieldObject( 354 parentItem, 355 definition, 356 definition.getJsonProperties(), 357 null, 358 getProblemHandler()); 359 } 360 361 @Override 362 public Object readItemFieldValue(IBoundObject parentItem, IBoundFieldValue fieldValue) throws IOException { 363 // read the field value's value 364 return checkMissingFieldValue(readScalarItem(fieldValue)); 365 } 366 367 @Nullable 368 private Object checkMissingFieldValue(Object value) throws IOException { 369 if (value == null && LOGGER.isWarnEnabled()) { 370 LOGGER.atWarn().log("Missing property value{}", 371 JsonUtil.generateLocationMessage(getReader(), getSource())); 372 } 373 // TODO: change nullness annotations to be @Nullable 374 return value; 375 } 376 377 @Override 378 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundInstanceModelAssembly instance) 379 throws IOException { 380 IBoundInstanceFlag jsonKey = instance.getJsonKey(); 381 IBoundDefinitionModelComplex definition = instance.getDefinition(); 382 return readComplexDefinitionObject( 383 parentItem, 384 definition, 385 jsonKey, 386 new PropertyBodyHandler(instance.getJsonProperties()), 387 getProblemHandler()); 388 } 389 390 @Override 391 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundInstanceModelGroupedAssembly instance) 392 throws IOException { 393 return readComplexDefinitionObject( 394 parentItem, 395 instance.getDefinition(), 396 instance.getEffectiveJsonKey(), 397 new PropertyBodyHandler(instance.getJsonProperties()), 398 new GroupedInstanceProblemHandler(instance, getProblemHandler())); 399 } 400 401 @Override 402 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundDefinitionModelAssembly definition) 403 throws IOException { 404 return readComplexDefinitionObject( 405 parentItem, 406 definition, 407 null, 408 new PropertyBodyHandler(definition.getJsonProperties()), 409 getProblemHandler()); 410 } 411 412 @NonNull 413 private Object readScalarItem(@NonNull IFeatureScalarItemValueHandler handler) 414 throws IOException { 415 return handler.getJavaTypeAdapter().parse(getReader(), getSource()); 416 } 417 418 @NonNull 419 private IBoundObject readFieldObject( 420 @Nullable IBoundObject parentItem, 421 @NonNull IBoundDefinitionModelFieldComplex definition, 422 @NonNull Map<String, IBoundProperty<?>> jsonProperties, 423 @Nullable IBoundInstanceFlag jsonKey, 424 @NonNull IJsonProblemHandler problemHandler) throws IOException { 425 IBoundInstanceFlag jsonValueKey = definition.getJsonValueKeyFlagInstance(); 426 IJsonProblemHandler actualProblemHandler = jsonValueKey == null 427 ? problemHandler 428 : new JsomValueKeyProblemHandler(problemHandler, jsonValueKey); 429 430 IBoundObject retval; 431 if (jsonProperties.isEmpty() && jsonValueKey == null) { 432 retval = readComplexDefinitionObject( 433 parentItem, 434 definition, 435 jsonKey, 436 (def, parent, problem) -> { 437 IBoundFieldValue fieldValue = definition.getFieldValue(); 438 Object item = readItemFieldValue(parent, fieldValue); 439 if (item != null) { 440 fieldValue.setValue(parent, item); 441 } 442 }, 443 actualProblemHandler); 444 445 } else { 446 retval = readComplexDefinitionObject( 447 parentItem, 448 definition, 449 jsonKey, 450 new PropertyBodyHandler(jsonProperties), 451 actualProblemHandler); 452 } 453 return retval; 454 } 455 456 @NonNull 457 private IBoundObject readComplexDefinitionObject( 458 @Nullable IBoundObject parentItem, 459 @NonNull IBoundDefinitionModelComplex definition, 460 @Nullable IBoundInstanceFlag jsonKey, 461 @NonNull DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler, 462 @NonNull IJsonProblemHandler problemHandler) throws IOException { 463 DefinitionBodyHandler<IBoundDefinitionModelComplex> actualBodyHandler = jsonKey == null 464 ? bodyHandler 465 : new JsonKeyBodyHandler(jsonKey, bodyHandler); 466 467 @SuppressWarnings("PMD.CloseResource") 468 JsonLocation location = getReader().currentLocation(); 469 470 // construct the item 471 IBoundObject item = definition.newInstance( 472 JsonLocation.NA.equals(location) 473 ? null 474 : () -> new MetaschemaData(ObjectUtils.requireNonNull(location))); 475 476 try { 477 // call pre-parse initialization hook 478 definition.callBeforeDeserialize(item, parentItem); 479 480 // read the property values 481 actualBodyHandler.accept(definition, item, problemHandler); 482 483 // call post-parse initialization hook 484 definition.callAfterDeserialize(item, parentItem); 485 } catch (BindingException ex) { 486 throw new IOException(ex); 487 } 488 489 return item; 490 } 491 492 @SuppressWarnings("resource") 493 @Override 494 public IBoundObject readChoiceGroupItem(IBoundObject parentItem, IBoundInstanceModelChoiceGroup instance) 495 throws IOException { 496 @SuppressWarnings("PMD.CloseResource") 497 JsonParser parser = getReader(); 498 ObjectNode node = parser.readValueAsTree(); 499 500 String discriminatorProperty = instance.getJsonDiscriminatorProperty(); 501 JsonNode discriminatorNode = node.get(discriminatorProperty); 502 if (discriminatorNode == null) { 503 throw new IllegalArgumentException(String.format( 504 "Unable to find discriminator property '%s' for object at '%s'.", 505 discriminatorProperty, 506 JsonUtil.toString(parser, getSource()))); 507 } 508 String discriminator = ObjectUtils.requireNonNull(discriminatorNode.asText()); 509 510 IBoundInstanceModelGroupedNamed actualInstance = instance.getGroupedModelInstance(discriminator); 511 assert actualInstance != null; 512 513 IBoundObject retval; 514 try (JsonParser newParser = node.traverse(parser.getCodec())) { 515 assert newParser != null; 516 push(newParser); 517 518 // get initial token 519 retval = actualInstance.readItem(parentItem, this); 520 assert newParser.currentToken() == null; 521 pop(newParser); 522 } 523 524 // advance the original parser to the next token 525 parser.nextToken(); 526 527 return retval; 528 } 529 530 private final class JsonKeyBodyHandler implements DefinitionBodyHandler<IBoundDefinitionModelComplex> { 531 @NonNull 532 private final IBoundInstanceFlag jsonKey; 533 @NonNull 534 private final DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler; 535 536 private JsonKeyBodyHandler( 537 @NonNull IBoundInstanceFlag jsonKey, 538 @NonNull DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler) { 539 this.jsonKey = jsonKey; 540 this.bodyHandler = bodyHandler; 541 } 542 543 @Override 544 public void accept( 545 IBoundDefinitionModelComplex definition, 546 IBoundObject parent, 547 IJsonProblemHandler problemHandler) 548 throws IOException { 549 @SuppressWarnings("PMD.CloseResource") 550 JsonParser parser = getReader(); 551 URI resource = getSource(); 552 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME); 553 554 // the field will be the JSON key 555 String key = ObjectUtils.notNull(parser.currentName()); 556 try { 557 Object value = jsonKey.getDefinition().getJavaTypeAdapter().parse(key); 558 jsonKey.setValue(parent, ObjectUtils.notNull(value.toString())); 559 } catch (IllegalArgumentException ex) { 560 throw new IOException( 561 String.format("Malformed data '%s'%s. %s", 562 key, 563 JsonUtil.generateLocationMessage(parser, resource), 564 ex.getLocalizedMessage()), 565 ex); 566 } 567 568 // skip to the next token 569 parser.nextToken(); 570 // JsonUtil.assertCurrent(parser, JsonToken.START_OBJECT); 571 572 // // advance past the JSON key's start object 573 // JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT); 574 575 // read the property values 576 bodyHandler.accept(definition, parent, problemHandler); 577 578 // // advance past the JSON key's end object 579 // JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT); 580 } 581 } 582 583 private final class PropertyBodyHandler implements DefinitionBodyHandler<IBoundDefinitionModelComplex> { 584 @NonNull 585 private final Map<String, IBoundProperty<?>> jsonProperties; 586 587 private PropertyBodyHandler(@NonNull Map<String, IBoundProperty<?>> jsonProperties) { 588 this.jsonProperties = jsonProperties; 589 } 590 591 @Override 592 public void accept( 593 IBoundDefinitionModelComplex definition, 594 IBoundObject parent, 595 IJsonProblemHandler problemHandler) 596 throws IOException { 597 @SuppressWarnings("PMD.CloseResource") 598 JsonParser parser = getReader(); 599 URI resource = getSource(); 600 601 // advance past the start object 602 JsonUtil.assertAndAdvance(parser, resource, JsonToken.START_OBJECT); 603 604 // make a copy, since we use the remaining values to initialize default values 605 Map<String, IBoundProperty<?>> remainingInstances = new HashMap<>(jsonProperties); // NOPMD not concurrent 606 607 // handle each property 608 while (JsonToken.FIELD_NAME.equals(parser.currentToken())) { 609 610 // the parser's current token should be the JSON field name 611 String propertyName = ObjectUtils.notNull(parser.currentName()); 612 if (LOGGER.isTraceEnabled()) { 613 LOGGER.trace("reading property {}", propertyName); 614 } 615 616 IBoundProperty<?> property = remainingInstances.get(propertyName); 617 618 boolean handled = false; 619 if (property != null) { 620 // advance past the field name 621 parser.nextToken(); 622 623 Object value = readObjectProperty(parent, property); 624 if (value != null) { 625 property.setValue(parent, value); 626 } 627 628 // mark handled 629 remainingInstances.remove(propertyName); 630 handled = true; 631 } 632 633 if (!handled && !problemHandler.handleUnknownProperty( 634 definition, 635 parent, 636 propertyName, 637 MetaschemaJsonReader.this)) { 638 if (LOGGER.isWarnEnabled()) { 639 LOGGER.warn("Skipping unhandled JSON field '{}' {}.", propertyName, JsonUtil.toString(parser, resource)); 640 } 641 JsonUtil.assertAndAdvance(parser, resource, JsonToken.FIELD_NAME); 642 JsonUtil.skipNextValue(parser, resource); 643 } 644 645 // the current token will be either the next instance field name or the end of 646 // the parent object 647 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); 648 } 649 650 problemHandler.handleMissingInstances( 651 definition, 652 parent, 653 ObjectUtils.notNull(remainingInstances.values())); 654 655 // advance past the end object 656 JsonUtil.assertAndAdvance(parser, resource, JsonToken.END_OBJECT); 657 } 658 } 659 660 private static final class GroupedInstanceProblemHandler implements IJsonProblemHandler { 661 @NonNull 662 private final IBoundInstanceModelGroupedNamed instance; 663 @NonNull 664 private final IJsonProblemHandler delegate; 665 666 private GroupedInstanceProblemHandler( 667 @NonNull IBoundInstanceModelGroupedNamed instance, 668 @NonNull IJsonProblemHandler delegate) { 669 this.instance = instance; 670 this.delegate = delegate; 671 } 672 673 @Override 674 public void handleMissingInstances( 675 IBoundDefinitionModelComplex parentDefinition, 676 IBoundObject targetObject, 677 Collection<? extends IBoundProperty<?>> unhandledInstances) throws IOException { 678 delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances); 679 } 680 681 @Override 682 public boolean handleUnknownProperty( 683 IBoundDefinitionModelComplex definition, 684 IBoundObject parentItem, 685 String fieldName, 686 IJsonParsingContext parsingContext) throws IOException { 687 boolean retval; 688 if (instance.getParentContainer().getJsonDiscriminatorProperty().equals(fieldName)) { 689 JsonUtil.skipNextValue(parsingContext.getReader(), parsingContext.getSource()); 690 retval = true; 691 } else { 692 retval = delegate.handleUnknownProperty(definition, parentItem, fieldName, parsingContext); 693 } 694 return retval; 695 } 696 } 697 698 private final class JsomValueKeyProblemHandler implements IJsonProblemHandler { 699 @NonNull 700 private final IJsonProblemHandler delegate; 701 @NonNull 702 private final IBoundInstanceFlag jsonValueKeyFlag; 703 private boolean foundJsonValueKey; // false 704 705 private JsomValueKeyProblemHandler( 706 @NonNull IJsonProblemHandler delegate, 707 @NonNull IBoundInstanceFlag jsonValueKeyFlag) { 708 this.delegate = delegate; 709 this.jsonValueKeyFlag = jsonValueKeyFlag; 710 } 711 712 @Override 713 public void handleMissingInstances( 714 IBoundDefinitionModelComplex parentDefinition, 715 IBoundObject targetObject, 716 Collection<? extends IBoundProperty<?>> unhandledInstances) throws IOException { 717 delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances); 718 } 719 720 @Override 721 public boolean handleUnknownProperty( 722 IBoundDefinitionModelComplex definition, 723 IBoundObject parentItem, 724 String fieldName, 725 IJsonParsingContext parsingContext) throws IOException { 726 boolean retval; 727 if (foundJsonValueKey) { 728 retval = delegate.handleUnknownProperty(definition, parentItem, fieldName, parsingContext); 729 } else { 730 @SuppressWarnings("PMD.CloseResource") 731 JsonParser parser = parsingContext.getReader(); 732 URI resource = parsingContext.getSource(); 733 734 // handle JSON value key 735 String key = ObjectUtils.notNull(parser.currentName()); 736 try { 737 Object keyValue = jsonValueKeyFlag.getJavaTypeAdapter().parse(key); 738 jsonValueKeyFlag.setValue(ObjectUtils.notNull(parentItem), keyValue); 739 } catch (IllegalArgumentException ex) { 740 throw new IOException( 741 String.format("Malformed data '%s'%s. %s", 742 key, 743 JsonUtil.generateLocationMessage(parser, resource), 744 ex.getLocalizedMessage()), 745 ex); 746 } 747 // advance past the field name 748 JsonUtil.assertAndAdvance(parser, resource, JsonToken.FIELD_NAME); 749 750 IBoundFieldValue fieldValue = ((IBoundDefinitionModelFieldComplex) definition).getFieldValue(); 751 Object value = readItemFieldValue(ObjectUtils.notNull(parentItem), fieldValue); 752 if (value != null) { 753 fieldValue.setValue(ObjectUtils.notNull(parentItem), value); 754 } 755 756 retval = foundJsonValueKey = true; 757 } 758 return retval; 759 } 760 } 761 762 private class ModelInstanceReadHandler<ITEM> 763 extends AbstractModelInstanceReadHandler<ITEM> { 764 765 protected ModelInstanceReadHandler( 766 @NonNull IBoundInstanceModel<ITEM> instance, 767 @NonNull IBoundObject parentItem) { 768 super(instance, parentItem); 769 } 770 771 @Override 772 public List<ITEM> readList() throws IOException { 773 @SuppressWarnings("PMD.CloseResource") 774 JsonParser parser = getReader(); 775 URI resource = getSource(); 776 777 List<ITEM> items = new LinkedList<>(); 778 switch (parser.currentToken()) { 779 case START_ARRAY: 780 // this is an array, we need to parse the array wrapper then each item 781 JsonUtil.assertAndAdvance(parser, resource, JsonToken.START_ARRAY); 782 783 // parse items 784 while (!JsonToken.END_ARRAY.equals(parser.currentToken())) { 785 items.add(readItem()); 786 } 787 788 // this is the other side of the array wrapper, advance past it 789 JsonUtil.assertAndAdvance(parser, resource, JsonToken.END_ARRAY); 790 break; 791 case VALUE_NULL: 792 JsonUtil.assertAndAdvance(parser, resource, JsonToken.VALUE_NULL); 793 break; 794 default: 795 // this is a singleton, just parse the value as a single item 796 items.add(readItem()); 797 break; 798 } 799 return items; 800 } 801 802 @Override 803 public Map<String, ITEM> readMap() throws IOException { 804 @SuppressWarnings("PMD.CloseResource") 805 JsonParser parser = getReader(); 806 URI resource = getSource(); 807 808 IBoundInstanceModel<?> instance = getCollectionInfo().getInstance(); 809 810 @SuppressWarnings("PMD.UseConcurrentHashMap") 811 Map<String, ITEM> items = new LinkedHashMap<>(); 812 813 // A map value is always wrapped in a START_OBJECT, since fields are used for 814 // the keys 815 JsonUtil.assertAndAdvance(parser, resource, JsonToken.START_OBJECT); 816 817 // process all map items 818 while (!JsonToken.END_OBJECT.equals(parser.currentToken())) { 819 820 // a map item will always start with a FIELD_NAME, since this represents the key 821 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME); 822 823 // get the object, since it must have a JSON key 824 ITEM item = readItem(); 825 if (item == null) { 826 throw new IOException(String.format("Null object encountered'%s.", 827 JsonUtil.generateLocationMessage(parser, resource))); 828 } 829 830 // lookup the key 831 IBoundInstanceFlag jsonKey = instance.getItemJsonKey(item); 832 assert jsonKey != null; 833 834 Object keyValue = jsonKey.getValue(item); 835 if (keyValue == null) { 836 throw new IOException(String.format("Null value for json-key for definition '%s'", 837 jsonKey.getContainingDefinition().toCoordinates())); 838 } 839 String key; 840 try { 841 key = jsonKey.getJavaTypeAdapter().asString(keyValue); 842 } catch (IllegalArgumentException ex) { 843 throw new IOException( 844 String.format("Malformed data '%s'%s. %s", 845 keyValue, 846 JsonUtil.generateLocationMessage(parser, resource), 847 ex.getLocalizedMessage()), 848 ex); 849 } 850 items.put(key, item); 851 852 // the next item will be a FIELD_NAME, or we will encounter an END_OBJECT if all 853 // items have been 854 // read 855 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); 856 } 857 858 // A map value will always end with an end object, which needs to be consumed 859 JsonUtil.assertAndAdvance(parser, resource, JsonToken.END_OBJECT); 860 861 return items; 862 } 863 864 @Override 865 public ITEM readItem() throws IOException { 866 IBoundInstanceModel<ITEM> instance = getCollectionInfo().getInstance(); 867 return instance.readItem(getParentObject(), MetaschemaJsonReader.this); 868 } 869 } 870 871 @SuppressWarnings("PMD.DataClass") 872 private static class MetaschemaData implements IMetaschemaData { 873 private final int line; 874 private final int column; 875 private final long charOffset; 876 private final long byteOffset; 877 878 public MetaschemaData(@NonNull JsonLocation location) { 879 this.line = location.getLineNr(); 880 this.column = location.getColumnNr(); 881 this.charOffset = location.getCharOffset(); 882 this.byteOffset = location.getByteOffset(); 883 } 884 885 @Override 886 public int getLine() { 887 return line; 888 } 889 890 @Override 891 public int getColumn() { 892 return column; 893 } 894 895 @Override 896 public long getCharOffset() { 897 return charOffset; 898 } 899 900 @Override 901 public long getByteOffset() { 902 return byteOffset; 903 } 904 } 905 906 @FunctionalInterface 907 private interface DefinitionBodyHandler<DEF extends IBoundDefinitionModelComplex> { 908 void accept( 909 @NonNull DEF definition, 910 @NonNull IBoundObject parent, 911 @NonNull IJsonProblemHandler problemHandler) throws IOException; 912 } 913}