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) { 369 if (value == null && LOGGER.isWarnEnabled()) { 370 LOGGER.atWarn().log("Missing property value{}", 371 JsonUtil.generateLocationMessage(getReader(), getSource())); 372 } 373 return value; 374 } 375 376 @Override 377 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundInstanceModelAssembly instance) 378 throws IOException { 379 IBoundInstanceFlag jsonKey = instance.getJsonKey(); 380 IBoundDefinitionModelComplex definition = instance.getDefinition(); 381 return readComplexDefinitionObject( 382 parentItem, 383 definition, 384 jsonKey, 385 new PropertyBodyHandler(instance.getJsonProperties()), 386 getProblemHandler()); 387 } 388 389 @Override 390 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundInstanceModelGroupedAssembly instance) 391 throws IOException { 392 return readComplexDefinitionObject( 393 parentItem, 394 instance.getDefinition(), 395 instance.getEffectiveJsonKey(), 396 new PropertyBodyHandler(instance.getJsonProperties()), 397 new GroupedInstanceProblemHandler(instance, getProblemHandler())); 398 } 399 400 @Override 401 public IBoundObject readItemAssembly(IBoundObject parentItem, IBoundDefinitionModelAssembly definition) 402 throws IOException { 403 return readComplexDefinitionObject( 404 parentItem, 405 definition, 406 null, 407 new PropertyBodyHandler(definition.getJsonProperties()), 408 getProblemHandler()); 409 } 410 411 @NonNull 412 private Object readScalarItem(@NonNull IFeatureScalarItemValueHandler handler) 413 throws IOException { 414 return handler.getJavaTypeAdapter().parse(getReader(), getSource()); 415 } 416 417 @NonNull 418 private IBoundObject readFieldObject( 419 @Nullable IBoundObject parentItem, 420 @NonNull IBoundDefinitionModelFieldComplex definition, 421 @NonNull Map<String, IBoundProperty<?>> jsonProperties, 422 @Nullable IBoundInstanceFlag jsonKey, 423 @NonNull IJsonProblemHandler problemHandler) throws IOException { 424 IBoundInstanceFlag jsonValueKey = definition.getJsonValueKeyFlagInstance(); 425 IJsonProblemHandler actualProblemHandler = jsonValueKey == null 426 ? problemHandler 427 : new JsomValueKeyProblemHandler(problemHandler, jsonValueKey); 428 429 IBoundObject retval; 430 if (jsonProperties.isEmpty() && jsonValueKey == null) { 431 retval = readComplexDefinitionObject( 432 parentItem, 433 definition, 434 jsonKey, 435 (def, parent, problem) -> { 436 IBoundFieldValue fieldValue = definition.getFieldValue(); 437 Object item = readItemFieldValue(parent, fieldValue); 438 if (item != null) { 439 fieldValue.setValue(parent, item); 440 } 441 }, 442 actualProblemHandler); 443 444 } else { 445 retval = readComplexDefinitionObject( 446 parentItem, 447 definition, 448 jsonKey, 449 new PropertyBodyHandler(jsonProperties), 450 actualProblemHandler); 451 } 452 return retval; 453 } 454 455 @NonNull 456 private IBoundObject readComplexDefinitionObject( 457 @Nullable IBoundObject parentItem, 458 @NonNull IBoundDefinitionModelComplex definition, 459 @Nullable IBoundInstanceFlag jsonKey, 460 @NonNull DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler, 461 @NonNull IJsonProblemHandler problemHandler) throws IOException { 462 DefinitionBodyHandler<IBoundDefinitionModelComplex> actualBodyHandler = jsonKey == null 463 ? bodyHandler 464 : new JsonKeyBodyHandler(jsonKey, bodyHandler); 465 466 @SuppressWarnings("PMD.CloseResource") 467 JsonLocation location = getReader().currentLocation(); 468 469 // construct the item 470 IBoundObject item = definition.newInstance( 471 JsonLocation.NA.equals(location) 472 ? null 473 : () -> new MetaschemaData(ObjectUtils.requireNonNull(location))); 474 475 try { 476 // call pre-parse initialization hook 477 definition.callBeforeDeserialize(item, parentItem); 478 479 // read the property values 480 actualBodyHandler.accept(definition, item, problemHandler); 481 482 // call post-parse initialization hook 483 definition.callAfterDeserialize(item, parentItem); 484 } catch (BindingException ex) { 485 throw new IOException(ex); 486 } 487 488 return item; 489 } 490 491 @SuppressWarnings("resource") 492 @Override 493 public IBoundObject readChoiceGroupItem(IBoundObject parentItem, IBoundInstanceModelChoiceGroup instance) 494 throws IOException { 495 @SuppressWarnings("PMD.CloseResource") 496 JsonParser parser = getReader(); 497 ObjectNode node = parser.readValueAsTree(); 498 499 String discriminatorProperty = instance.getJsonDiscriminatorProperty(); 500 JsonNode discriminatorNode = node.get(discriminatorProperty); 501 if (discriminatorNode == null) { 502 throw new IllegalArgumentException(String.format( 503 "Unable to find discriminator property '%s' for object at '%s'.", 504 discriminatorProperty, 505 JsonUtil.toString(parser, getSource()))); 506 } 507 String discriminator = ObjectUtils.requireNonNull(discriminatorNode.asText()); 508 509 IBoundInstanceModelGroupedNamed actualInstance = instance.getGroupedModelInstance(discriminator); 510 assert actualInstance != null; 511 512 IBoundObject retval; 513 try (JsonParser newParser = node.traverse(parser.getCodec())) { 514 assert newParser != null; 515 push(newParser); 516 517 // get initial token 518 retval = actualInstance.readItem(parentItem, this); 519 assert newParser.currentToken() == null; 520 pop(newParser); 521 } 522 523 // advance the original parser to the next token 524 parser.nextToken(); 525 526 return retval; 527 } 528 529 private final class JsonKeyBodyHandler implements DefinitionBodyHandler<IBoundDefinitionModelComplex> { 530 @NonNull 531 private final IBoundInstanceFlag jsonKey; 532 @NonNull 533 private final DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler; 534 535 private JsonKeyBodyHandler( 536 @NonNull IBoundInstanceFlag jsonKey, 537 @NonNull DefinitionBodyHandler<IBoundDefinitionModelComplex> bodyHandler) { 538 this.jsonKey = jsonKey; 539 this.bodyHandler = bodyHandler; 540 } 541 542 @Override 543 public void accept( 544 IBoundDefinitionModelComplex definition, 545 IBoundObject parent, 546 IJsonProblemHandler problemHandler) 547 throws IOException { 548 @SuppressWarnings("PMD.CloseResource") 549 JsonParser parser = getReader(); 550 URI resource = getSource(); 551 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME); 552 553 // the field will be the JSON key 554 String key = ObjectUtils.notNull(parser.currentName()); 555 try { 556 Object value = jsonKey.getDefinition().getJavaTypeAdapter().parse(key); 557 jsonKey.setValue(parent, ObjectUtils.notNull(value.toString())); 558 } catch (IllegalArgumentException ex) { 559 throw new IOException( 560 String.format("Malformed data '%s'%s. %s", 561 key, 562 JsonUtil.generateLocationMessage(parser, resource), 563 ex.getLocalizedMessage()), 564 ex); 565 } 566 567 // skip to the next token 568 parser.nextToken(); 569 // JsonUtil.assertCurrent(parser, JsonToken.START_OBJECT); 570 571 // // advance past the JSON key's start object 572 // JsonUtil.assertAndAdvance(parser, JsonToken.START_OBJECT); 573 574 // read the property values 575 bodyHandler.accept(definition, parent, problemHandler); 576 577 // // advance past the JSON key's end object 578 // JsonUtil.assertAndAdvance(parser, JsonToken.END_OBJECT); 579 } 580 } 581 582 private final class PropertyBodyHandler implements DefinitionBodyHandler<IBoundDefinitionModelComplex> { 583 @NonNull 584 private final Map<String, IBoundProperty<?>> jsonProperties; 585 586 private PropertyBodyHandler(@NonNull Map<String, IBoundProperty<?>> jsonProperties) { 587 this.jsonProperties = jsonProperties; 588 } 589 590 @Override 591 public void accept( 592 IBoundDefinitionModelComplex definition, 593 IBoundObject parent, 594 IJsonProblemHandler problemHandler) 595 throws IOException { 596 @SuppressWarnings("PMD.CloseResource") 597 JsonParser parser = getReader(); 598 URI resource = getSource(); 599 600 // advance past the start object 601 JsonUtil.assertAndAdvance(parser, resource, JsonToken.START_OBJECT); 602 603 // make a copy, since we use the remaining values to initialize default values 604 Map<String, IBoundProperty<?>> remainingInstances = new HashMap<>(jsonProperties); // NOPMD not concurrent 605 606 // handle each property 607 while (JsonToken.FIELD_NAME.equals(parser.currentToken())) { 608 609 // the parser's current token should be the JSON field name 610 String propertyName = ObjectUtils.notNull(parser.currentName()); 611 if (LOGGER.isTraceEnabled()) { 612 LOGGER.trace("reading property {}", propertyName); 613 } 614 615 IBoundProperty<?> property = remainingInstances.get(propertyName); 616 617 boolean handled = false; 618 if (property != null) { 619 // advance past the field name 620 parser.nextToken(); 621 622 Object value = readObjectProperty(parent, property); 623 if (value != null) { 624 property.setValue(parent, value); 625 } 626 627 // mark handled 628 remainingInstances.remove(propertyName); 629 handled = true; 630 } 631 632 if (!handled && !problemHandler.handleUnknownProperty( 633 definition, 634 parent, 635 propertyName, 636 MetaschemaJsonReader.this)) { 637 if (LOGGER.isWarnEnabled()) { 638 LOGGER.warn("Skipping unhandled JSON field '{}' {}.", propertyName, JsonUtil.toString(parser, resource)); 639 } 640 JsonUtil.assertAndAdvance(parser, resource, JsonToken.FIELD_NAME); 641 JsonUtil.skipNextValue(parser, resource); 642 } 643 644 // the current token will be either the next instance field name or the end of 645 // the parent object 646 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); 647 } 648 649 problemHandler.handleMissingInstances( 650 definition, 651 parent, 652 ObjectUtils.notNull(remainingInstances.values())); 653 654 // advance past the end object 655 JsonUtil.assertAndAdvance(parser, resource, JsonToken.END_OBJECT); 656 } 657 } 658 659 private static final class GroupedInstanceProblemHandler implements IJsonProblemHandler { 660 @NonNull 661 private final IBoundInstanceModelGroupedNamed instance; 662 @NonNull 663 private final IJsonProblemHandler delegate; 664 665 private GroupedInstanceProblemHandler( 666 @NonNull IBoundInstanceModelGroupedNamed instance, 667 @NonNull IJsonProblemHandler delegate) { 668 this.instance = instance; 669 this.delegate = delegate; 670 } 671 672 @Override 673 public void handleMissingInstances( 674 IBoundDefinitionModelComplex parentDefinition, 675 IBoundObject targetObject, 676 Collection<? extends IBoundProperty<?>> unhandledInstances) throws IOException { 677 delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances); 678 } 679 680 @Override 681 public boolean handleUnknownProperty( 682 IBoundDefinitionModelComplex definition, 683 IBoundObject parentItem, 684 String fieldName, 685 IJsonParsingContext parsingContext) throws IOException { 686 boolean retval; 687 if (instance.getParentContainer().getJsonDiscriminatorProperty().equals(fieldName)) { 688 JsonUtil.skipNextValue(parsingContext.getReader(), parsingContext.getSource()); 689 retval = true; 690 } else { 691 retval = delegate.handleUnknownProperty(definition, parentItem, fieldName, parsingContext); 692 } 693 return retval; 694 } 695 } 696 697 private final class JsomValueKeyProblemHandler implements IJsonProblemHandler { 698 @NonNull 699 private final IJsonProblemHandler delegate; 700 @NonNull 701 private final IBoundInstanceFlag jsonValueKeyFlag; 702 private boolean foundJsonValueKey; // false 703 704 private JsomValueKeyProblemHandler( 705 @NonNull IJsonProblemHandler delegate, 706 @NonNull IBoundInstanceFlag jsonValueKeyFlag) { 707 this.delegate = delegate; 708 this.jsonValueKeyFlag = jsonValueKeyFlag; 709 } 710 711 @Override 712 public void handleMissingInstances( 713 IBoundDefinitionModelComplex parentDefinition, 714 IBoundObject targetObject, 715 Collection<? extends IBoundProperty<?>> unhandledInstances) throws IOException { 716 delegate.handleMissingInstances(parentDefinition, targetObject, unhandledInstances); 717 } 718 719 @Override 720 public boolean handleUnknownProperty( 721 IBoundDefinitionModelComplex definition, 722 IBoundObject parentItem, 723 String fieldName, 724 IJsonParsingContext parsingContext) throws IOException { 725 boolean retval; 726 if (foundJsonValueKey) { 727 retval = delegate.handleUnknownProperty(definition, parentItem, fieldName, parsingContext); 728 } else { 729 @SuppressWarnings("PMD.CloseResource") 730 JsonParser parser = parsingContext.getReader(); 731 URI resource = parsingContext.getSource(); 732 733 // handle JSON value key 734 String key = ObjectUtils.notNull(parser.currentName()); 735 try { 736 Object keyValue = jsonValueKeyFlag.getJavaTypeAdapter().parse(key); 737 jsonValueKeyFlag.setValue(ObjectUtils.notNull(parentItem), keyValue); 738 } catch (IllegalArgumentException ex) { 739 throw new IOException( 740 String.format("Malformed data '%s'%s. %s", 741 key, 742 JsonUtil.generateLocationMessage(parser, resource), 743 ex.getLocalizedMessage()), 744 ex); 745 } 746 // advance past the field name 747 JsonUtil.assertAndAdvance(parser, resource, JsonToken.FIELD_NAME); 748 749 IBoundFieldValue fieldValue = ((IBoundDefinitionModelFieldComplex) definition).getFieldValue(); 750 Object value = readItemFieldValue(ObjectUtils.notNull(parentItem), fieldValue); 751 if (value != null) { 752 fieldValue.setValue(ObjectUtils.notNull(parentItem), value); 753 } 754 755 retval = foundJsonValueKey = true; 756 } 757 return retval; 758 } 759 } 760 761 private class ModelInstanceReadHandler<ITEM> 762 extends AbstractModelInstanceReadHandler<ITEM> { 763 764 protected ModelInstanceReadHandler( 765 @NonNull IBoundInstanceModel<ITEM> instance, 766 @NonNull IBoundObject parentItem) { 767 super(instance, parentItem); 768 } 769 770 @Override 771 public List<ITEM> readList() throws IOException { 772 @SuppressWarnings("PMD.CloseResource") 773 JsonParser parser = getReader(); 774 URI resource = getSource(); 775 776 List<ITEM> items = new LinkedList<>(); 777 switch (parser.currentToken()) { 778 case START_ARRAY: 779 // this is an array, we need to parse the array wrapper then each item 780 JsonUtil.assertAndAdvance(parser, resource, JsonToken.START_ARRAY); 781 782 // parse items 783 while (!JsonToken.END_ARRAY.equals(parser.currentToken())) { 784 items.add(readItem()); 785 } 786 787 // this is the other side of the array wrapper, advance past it 788 JsonUtil.assertAndAdvance(parser, resource, JsonToken.END_ARRAY); 789 break; 790 case VALUE_NULL: 791 JsonUtil.assertAndAdvance(parser, resource, JsonToken.VALUE_NULL); 792 break; 793 default: 794 // this is a singleton, just parse the value as a single item 795 items.add(readItem()); 796 break; 797 } 798 return items; 799 } 800 801 @Override 802 public Map<String, ITEM> readMap() throws IOException { 803 @SuppressWarnings("PMD.CloseResource") 804 JsonParser parser = getReader(); 805 URI resource = getSource(); 806 807 IBoundInstanceModel<?> instance = getCollectionInfo().getInstance(); 808 809 @SuppressWarnings("PMD.UseConcurrentHashMap") 810 Map<String, ITEM> items = new LinkedHashMap<>(); 811 812 // A map value is always wrapped in a START_OBJECT, since fields are used for 813 // the keys 814 JsonUtil.assertAndAdvance(parser, resource, JsonToken.START_OBJECT); 815 816 // process all map items 817 while (!JsonToken.END_OBJECT.equals(parser.currentToken())) { 818 819 // a map item will always start with a FIELD_NAME, since this represents the key 820 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME); 821 822 // get the object, since it must have a JSON key 823 ITEM item = readItem(); 824 if (item == null) { 825 throw new IOException(String.format("Null object encountered'%s.", 826 JsonUtil.generateLocationMessage(parser, resource))); 827 } 828 829 // lookup the key 830 IBoundInstanceFlag jsonKey = instance.getItemJsonKey(item); 831 assert jsonKey != null; 832 833 Object keyValue = jsonKey.getValue(item); 834 if (keyValue == null) { 835 throw new IOException(String.format("Null value for json-key for definition '%s'", 836 jsonKey.getContainingDefinition().toCoordinates())); 837 } 838 String key; 839 try { 840 key = jsonKey.getJavaTypeAdapter().asString(keyValue); 841 } catch (IllegalArgumentException ex) { 842 throw new IOException( 843 String.format("Malformed data '%s'%s. %s", 844 keyValue, 845 JsonUtil.generateLocationMessage(parser, resource), 846 ex.getLocalizedMessage()), 847 ex); 848 } 849 items.put(key, item); 850 851 // the next item will be a FIELD_NAME, or we will encounter an END_OBJECT if all 852 // items have been 853 // read 854 JsonUtil.assertCurrent(parser, resource, JsonToken.FIELD_NAME, JsonToken.END_OBJECT); 855 } 856 857 // A map value will always end with an end object, which needs to be consumed 858 JsonUtil.assertAndAdvance(parser, resource, JsonToken.END_OBJECT); 859 860 return items; 861 } 862 863 @Override 864 public ITEM readItem() throws IOException { 865 IBoundInstanceModel<ITEM> instance = getCollectionInfo().getInstance(); 866 return instance.readItem(getParentObject(), MetaschemaJsonReader.this); 867 } 868 } 869 870 @SuppressWarnings("PMD.DataClass") 871 private static class MetaschemaData implements IMetaschemaData { 872 private final int line; 873 private final int column; 874 private final long charOffset; 875 private final long byteOffset; 876 877 public MetaschemaData(@NonNull JsonLocation location) { 878 this.line = location.getLineNr(); 879 this.column = location.getColumnNr(); 880 this.charOffset = location.getCharOffset(); 881 this.byteOffset = location.getByteOffset(); 882 } 883 884 @Override 885 public int getLine() { 886 return line; 887 } 888 889 @Override 890 public int getColumn() { 891 return column; 892 } 893 894 @Override 895 public long getCharOffset() { 896 return charOffset; 897 } 898 899 @Override 900 public long getByteOffset() { 901 return byteOffset; 902 } 903 } 904 905 @FunctionalInterface 906 private interface DefinitionBodyHandler<DEF extends IBoundDefinitionModelComplex> { 907 void accept( 908 @NonNull DEF definition, 909 @NonNull IBoundObject parent, 910 @NonNull IJsonProblemHandler problemHandler) throws IOException; 911 } 912}