1
2
3
4
5
6 package gov.nist.secauto.metaschema.databind.io.xml;
7
8 import gov.nist.secauto.metaschema.core.model.IBoundObject;
9 import gov.nist.secauto.metaschema.core.model.IMetaschemaData;
10 import gov.nist.secauto.metaschema.core.model.util.XmlEventUtil;
11 import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
12 import gov.nist.secauto.metaschema.core.util.CollectionUtil;
13 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
14 import gov.nist.secauto.metaschema.databind.io.BindingException;
15 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
16 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex;
17 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelFieldComplex;
18 import gov.nist.secauto.metaschema.databind.model.IBoundFieldValue;
19 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag;
20 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel;
21 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly;
22 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
23 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldComplex;
24 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldScalar;
25 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedAssembly;
26 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedField;
27 import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedNamed;
28 import gov.nist.secauto.metaschema.databind.model.info.AbstractModelInstanceReadHandler;
29 import gov.nist.secauto.metaschema.databind.model.info.IFeatureScalarItemValueHandler;
30 import gov.nist.secauto.metaschema.databind.model.info.IItemReadHandler;
31 import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo;
32
33 import org.apache.logging.log4j.LogManager;
34 import org.apache.logging.log4j.Logger;
35 import org.codehaus.stax2.XMLEventReader2;
36
37 import java.io.IOException;
38 import java.net.URI;
39 import java.util.Collection;
40 import java.util.HashSet;
41 import java.util.LinkedHashMap;
42 import java.util.LinkedList;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.function.Function;
47 import java.util.stream.Collectors;
48
49 import javax.xml.namespace.QName;
50 import javax.xml.stream.Location;
51 import javax.xml.stream.XMLStreamConstants;
52 import javax.xml.stream.XMLStreamException;
53 import javax.xml.stream.events.Attribute;
54 import javax.xml.stream.events.StartElement;
55 import javax.xml.stream.events.XMLEvent;
56
57 import edu.umd.cs.findbugs.annotations.NonNull;
58 import edu.umd.cs.findbugs.annotations.Nullable;
59
60
61
62
63 @SuppressWarnings("PMD.CouplingBetweenObjects")
64 public class MetaschemaXmlReader
65 implements IXmlParsingContext {
66 private static final Logger LOGGER = LogManager.getLogger(MetaschemaXmlReader.class);
67 @NonNull
68 private final XMLEventReader2 reader;
69 @NonNull
70 private final URI source;
71 @NonNull
72 private final IXmlProblemHandler problemHandler;
73
74
75
76
77
78
79
80
81
82
83 public MetaschemaXmlReader(
84 @NonNull XMLEventReader2 reader,
85 @NonNull URI source) {
86 this(reader, source, new DefaultXmlProblemHandler());
87 }
88
89
90
91
92
93
94
95
96
97
98
99 public MetaschemaXmlReader(
100 @NonNull XMLEventReader2 reader,
101 @NonNull URI source,
102 @NonNull IXmlProblemHandler problemHandler) {
103 this.reader = reader;
104 this.source = source;
105 this.problemHandler = problemHandler;
106 }
107
108 @Override
109 public XMLEventReader2 getReader() {
110 return reader;
111 }
112
113 @Override
114 public URI getSource() {
115 return source;
116 }
117
118 @Override
119 public IXmlProblemHandler getProblemHandler() {
120 return problemHandler;
121 }
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 @Override
138 @NonNull
139 public <CLASS> CLASS read(@NonNull IBoundDefinitionModelComplex definition) throws IOException {
140 URI resource = getSource();
141 try {
142
143 if (reader.peek().isStartDocument()) {
144 XmlEventUtil.consumeAndAssert(reader, resource, XMLStreamConstants.START_DOCUMENT);
145 }
146
147
148 XmlEventUtil.skipEvents(reader, XMLStreamConstants.CHARACTERS, XMLStreamConstants.PROCESSING_INSTRUCTION,
149 XMLStreamConstants.DTD);
150
151 XMLEvent event = ObjectUtils.requireNonNull(reader.peek());
152 if (!event.isStartElement()) {
153 throw new IOException(
154 String.format("The token '%s' is not an XML element%s.",
155 XmlEventUtil.toEventName(event),
156 XmlEventUtil.generateLocationMessage(event, resource)));
157 }
158
159 ItemReadHandler handler = new ItemReadHandler(ObjectUtils.notNull(event.asStartElement()));
160 Object value = definition.readItem(null, handler);
161 if (value == null) {
162 event = reader.peek();
163 throw new IOException(String.format("Unable to read data.%s",
164 event == null ? "" : XmlEventUtil.generateLocationMessage(event, resource)));
165 }
166
167 return ObjectUtils.asType(value);
168 } catch (XMLStreamException ex) {
169 throw new IOException(ex);
170 }
171 }
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188 protected void readFlagInstances(
189 @NonNull IBoundDefinitionModelComplex targetDefinition,
190 @NonNull IBoundObject targetObject,
191 @NonNull StartElement start) throws IOException, XMLStreamException {
192 URI resource = getSource();
193
194 Map<IEnhancedQName, IBoundInstanceFlag> flagInstanceMap = targetDefinition.getFlagInstances().stream()
195 .collect(Collectors.toMap(
196 IBoundInstanceFlag::getQName,
197 Function.identity()));
198
199 for (Attribute attribute : CollectionUtil.toIterable(ObjectUtils.notNull(start.getAttributes()))) {
200 IEnhancedQName qname = IEnhancedQName.of(ObjectUtils.requireNonNull(attribute.getName()));
201 IBoundInstanceFlag instance = flagInstanceMap.get(qname);
202 if (instance == null) {
203
204 if (!getProblemHandler().handleUnknownAttribute(targetDefinition, targetObject, attribute, this)) {
205 throw new IOException(
206 String.format("Unrecognized attribute '%s'%s.",
207 qname,
208 XmlEventUtil.generateLocationMessage(attribute, resource)));
209 }
210 } else {
211 try {
212
213 Object value = instance.getDefinition().getJavaTypeAdapter().parse(ObjectUtils.notNull(attribute.getValue()));
214
215 instance.setValue(targetObject, value);
216 flagInstanceMap.remove(qname);
217 } catch (IllegalArgumentException ex) {
218 throw new IOException(
219 String.format("Malformed data '%s'%s. %s",
220 attribute.getValue(),
221 XmlEventUtil.generateLocationMessage(start, resource),
222 ex.getLocalizedMessage()),
223 ex);
224 }
225 }
226 }
227
228 if (!flagInstanceMap.isEmpty()) {
229 getProblemHandler().handleMissingFlagInstances(
230 targetDefinition,
231 targetObject,
232 ObjectUtils.notNull(flagInstanceMap.values()));
233 }
234 }
235
236
237
238
239
240
241
242
243
244
245
246
247 protected void readModelInstances(
248 @NonNull IBoundDefinitionModelAssembly targetDefinition,
249 @NonNull IBoundObject targetObject)
250 throws IOException {
251 Collection<? extends IBoundInstanceModel<?>> instances = targetDefinition.getModelInstances();
252 Set<IBoundInstanceModel<?>> unhandledProperties = new HashSet<>();
253 for (IBoundInstanceModel<?> modelInstance : instances) {
254 assert modelInstance != null;
255 if (!readItems(modelInstance, targetObject, true)) {
256 unhandledProperties.add(modelInstance);
257 }
258 }
259
260
261 getProblemHandler().handleMissingModelInstances(targetDefinition, targetObject, unhandledProperties);
262
263 XMLEventReader2 reader = getReader();
264 URI resource = getSource();
265
266
267 try {
268 if (!getReader().peek().isEndElement()) {
269
270 XmlEventUtil.skipWhitespace(reader);
271 XmlEventUtil.skipElement(reader);
272 XmlEventUtil.skipWhitespace(reader);
273 }
274
275 XmlEventUtil.assertNext(reader, resource, XMLStreamConstants.END_ELEMENT);
276 } catch (XMLStreamException ex) {
277 throw new IOException(ex);
278 }
279 }
280
281
282
283
284
285
286
287
288
289
290
291 @SuppressWarnings("PMD.OnlyOneReturn")
292 protected boolean isNextInstance(
293 @NonNull IBoundInstanceModel<?> targetInstance)
294 throws XMLStreamException {
295
296 XmlEventUtil.skipWhitespace(reader);
297
298 XMLEvent nextEvent = reader.peek();
299
300 boolean retval = nextEvent.isStartElement();
301 if (retval) {
302 IEnhancedQName qname = IEnhancedQName.of(ObjectUtils.notNull(nextEvent.asStartElement().getName()));
303 retval = qname.equals(targetInstance.getEffectiveXmlGroupAsQName())
304 || targetInstance.canHandleXmlQName(qname);
305 }
306 return retval;
307 }
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322 @Override
323 public <T> boolean readItems(
324 @NonNull IBoundInstanceModel<T> instance,
325 @NonNull IBoundObject parentObject,
326 boolean parseGrouping)
327 throws IOException {
328 try {
329 boolean handled = isNextInstance(instance);
330 if (handled) {
331 XMLEventReader2 reader = getReader();
332 URI resource = getSource();
333
334
335
336 IEnhancedQName groupEQName = parseGrouping ? instance.getEffectiveXmlGroupAsQName() : null;
337 QName groupQName = groupEQName == null ? null : groupEQName.toQName();
338 if (groupQName != null) {
339
340 XmlEventUtil.requireStartElement(reader, resource, groupQName);
341 }
342
343 IModelInstanceCollectionInfo<T> collectionInfo = instance.getCollectionInfo();
344
345 ModelInstanceReadHandler<T> handler = new ModelInstanceReadHandler<>(instance, parentObject);
346
347
348 Object value = collectionInfo.readItems(handler);
349 if (value != null) {
350 instance.setValue(parentObject, value);
351 }
352
353
354 XmlEventUtil.skipWhitespace(reader);
355
356 if (groupQName != null) {
357
358 XmlEventUtil.requireEndElement(reader, resource, groupQName);
359 }
360 }
361 return handled;
362 } catch (XMLStreamException ex) {
363 throw new IOException(ex);
364 }
365 }
366
367 private final class ModelInstanceReadHandler<ITEM>
368 extends AbstractModelInstanceReadHandler<ITEM> {
369
370 private ModelInstanceReadHandler(
371 @NonNull IBoundInstanceModel<ITEM> instance,
372 @NonNull IBoundObject parentObject) {
373 super(instance, parentObject);
374 }
375
376 @Override
377 public List<ITEM> readList() throws IOException {
378 return ObjectUtils.notNull(readCollection());
379 }
380
381 @Override
382 public Map<String, ITEM> readMap() throws IOException {
383 IBoundInstanceModel<?> instance = getCollectionInfo().getInstance();
384
385 return ObjectUtils.notNull(readCollection().stream()
386 .collect(Collectors.toMap(
387 item -> {
388 assert item != null;
389
390 IBoundInstanceFlag jsonKey = instance.getItemJsonKey(item);
391 assert jsonKey != null;
392 return ObjectUtils.requireNonNull(jsonKey.getValue(item)).toString();
393 },
394 Function.identity(),
395 (t, u) -> u,
396 LinkedHashMap::new)));
397 }
398
399 @NonNull
400 private List<ITEM> readCollection() throws IOException {
401 List<ITEM> retval = new LinkedList<>();
402 XMLEventReader2 reader = getReader();
403 try {
404
405
406 XmlEventUtil.skipWhitespace(reader);
407
408 IBoundInstanceModel<?> instance = getCollectionInfo().getInstance();
409 XMLEvent event;
410 while ((event = reader.peek()).isStartElement()
411 && instance.canHandleXmlQName(
412 IEnhancedQName.of(ObjectUtils.notNull(event.asStartElement().getName())))) {
413
414
415 ITEM value = readItem();
416 retval.add(value);
417
418
419 XmlEventUtil.skipWhitespace(reader);
420 }
421 } catch (XMLStreamException ex) {
422 throw new IOException(ex);
423 }
424 return retval;
425 }
426
427 @Override
428 public ITEM readItem() throws IOException {
429 try {
430 return getCollectionInfo().getInstance().readItem(
431 getParentObject(),
432 new ItemReadHandler(ObjectUtils.notNull(getReader().peek().asStartElement())));
433 } catch (XMLStreamException ex) {
434 throw new IOException(ex);
435 }
436 }
437 }
438
439 private final class ItemReadHandler implements IItemReadHandler {
440 @NonNull
441 private final StartElement startElement;
442
443 private ItemReadHandler(@NonNull StartElement startElement) {
444 this.startElement = startElement;
445 }
446
447
448
449
450
451
452 @NonNull
453 private StartElement getStartElement() {
454 return startElement;
455 }
456
457 @NonNull
458 private <DEF extends IBoundDefinitionModelComplex> IBoundObject readDefinitionElement(
459 @NonNull DEF definition,
460 @NonNull StartElement start,
461 @NonNull IEnhancedQName expectedEQName,
462 @Nullable IBoundObject parent,
463 @NonNull DefinitionBodyHandler<DEF, IBoundObject> bodyHandler) throws IOException {
464 XMLEventReader2 reader = getReader();
465 URI resource = getSource();
466 QName expectedQName = expectedEQName.toQName();
467
468 try {
469
470 XmlEventUtil.requireStartElement(reader, resource, expectedQName);
471
472 Location location = start.getLocation();
473
474
475 IBoundObject item = definition.newInstance(location == null ? null : () -> new MetaschemaData(location));
476
477
478 definition.callBeforeDeserialize(item, parent);
479
480
481 readFlagInstances(definition, item, start);
482
483
484 bodyHandler.accept(definition, item);
485
486 XmlEventUtil.skipWhitespace(reader);
487
488
489 definition.callAfterDeserialize(item, parent);
490
491
492 XmlEventUtil.requireEndElement(reader, resource, expectedQName);
493 return ObjectUtils.asType(item);
494 } catch (BindingException | XMLStreamException ex) {
495 throw new IOException(ex);
496 }
497 }
498
499 @Override
500 public Object readItemFlag(
501 IBoundObject parent,
502 IBoundInstanceFlag flag) throws IOException {
503
504 throw new UnsupportedOperationException("should be handled by readFlagInstances()");
505 }
506
507 private void handleFieldDefinitionBody(
508 @NonNull IBoundDefinitionModelFieldComplex definition,
509 @NonNull IBoundObject item) throws IOException {
510 IBoundFieldValue fieldValue = definition.getFieldValue();
511
512
513 Object value = fieldValue.readItem(item, this);
514 if (value != null) {
515 fieldValue.setValue(item, value);
516 }
517 }
518
519 @Override
520 public Object readItemField(
521 IBoundObject parent,
522 IBoundInstanceModelFieldScalar instance)
523 throws IOException {
524 XMLEventReader2 reader = getReader();
525 URI resource = getSource();
526 try {
527 QName wrapper = null;
528 if (instance.isEffectiveValueWrappedInXml()) {
529 wrapper = instance.getQName().toQName();
530
531 XmlEventUtil.skipWhitespace(reader);
532 XmlEventUtil.requireStartElement(reader, resource, wrapper);
533 }
534
535 Object retval = readScalarItem(instance);
536
537 if (wrapper != null) {
538 XmlEventUtil.skipWhitespace(reader);
539
540 XmlEventUtil.requireEndElement(reader, resource, wrapper);
541 }
542 return retval;
543 } catch (XMLStreamException ex) {
544 throw new IOException(ex);
545 }
546 }
547
548 @Override
549 public IBoundObject readItemField(
550 IBoundObject parent,
551 IBoundInstanceModelFieldComplex instance)
552 throws IOException {
553 return readDefinitionElement(
554 instance.getDefinition(),
555 getStartElement(),
556 instance.getQName(),
557 parent,
558 this::handleFieldDefinitionBody);
559 }
560
561 @Override
562 public IBoundObject readItemField(IBoundObject parent, IBoundInstanceModelGroupedField instance)
563 throws IOException {
564 return readDefinitionElement(
565 instance.getDefinition(),
566 getStartElement(),
567 instance.getQName(),
568 parent,
569 this::handleFieldDefinitionBody);
570 }
571
572 @Override
573 public IBoundObject readItemField(
574 IBoundObject parent,
575 IBoundDefinitionModelFieldComplex definition) throws IOException {
576 return readDefinitionElement(
577 definition,
578 getStartElement(),
579 definition.getQName(),
580 parent,
581 this::handleFieldDefinitionBody);
582 }
583
584 @Override
585 public Object readItemFieldValue(
586 IBoundObject parent,
587 IBoundFieldValue fieldValue) throws IOException {
588 return checkMissingFieldValue(readScalarItem(fieldValue));
589 }
590
591 @Nullable
592 private Object checkMissingFieldValue(Object value) {
593 if (value == null && LOGGER.isWarnEnabled()) {
594 StartElement start = getStartElement();
595 LOGGER.atWarn().log("Missing property value{}",
596 XmlEventUtil.generateLocationMessage(start, getSource()));
597 }
598 return value;
599 }
600
601 private void handleAssemblyDefinitionBody(
602 @NonNull IBoundDefinitionModelAssembly definition,
603 @NonNull IBoundObject item) throws IOException {
604 readModelInstances(definition, item);
605 }
606
607 @Override
608 public IBoundObject readItemAssembly(
609 IBoundObject parent,
610 IBoundInstanceModelAssembly instance) throws IOException {
611 return readDefinitionElement(
612 instance.getDefinition(),
613 getStartElement(),
614 instance.getQName(),
615 parent,
616 this::handleAssemblyDefinitionBody);
617 }
618
619 @Override
620 public IBoundObject readItemAssembly(IBoundObject parent, IBoundInstanceModelGroupedAssembly instance)
621 throws IOException {
622 return readDefinitionElement(
623 instance.getDefinition(),
624 getStartElement(),
625 instance.getQName(),
626 parent,
627 this::handleAssemblyDefinitionBody);
628 }
629
630 @Override
631 public IBoundObject readItemAssembly(
632 IBoundObject parent,
633 IBoundDefinitionModelAssembly definition) throws IOException {
634 return readDefinitionElement(
635 definition,
636 getStartElement(),
637 ObjectUtils.requireNonNull(definition.getRootQName()),
638 parent,
639 this::handleAssemblyDefinitionBody);
640 }
641
642 @Nullable
643 private Object readScalarItem(@NonNull IFeatureScalarItemValueHandler handler)
644 throws IOException {
645 return handler.getJavaTypeAdapter().parse(getReader(), getSource());
646 }
647
648 @Override
649 public IBoundObject readChoiceGroupItem(IBoundObject parent, IBoundInstanceModelChoiceGroup instance)
650 throws IOException {
651 try {
652 XMLEventReader2 eventReader = getReader();
653
654 XmlEventUtil.skipWhitespace(eventReader);
655
656 XMLEvent event = eventReader.peek();
657 IEnhancedQName nextQName = IEnhancedQName.of(ObjectUtils.notNull(event.asStartElement().getName()));
658 IBoundInstanceModelGroupedNamed actualInstance = instance.getGroupedModelInstance(nextQName);
659 assert actualInstance != null;
660 return actualInstance.readItem(parent, this);
661 } catch (XMLStreamException ex) {
662 throw new IOException(ex);
663 }
664 }
665 }
666
667 private static class MetaschemaData implements IMetaschemaData {
668 private final int line;
669 private final int column;
670 private final long charOffset;
671
672 public MetaschemaData(@NonNull Location location) {
673 this.line = location.getLineNumber();
674 this.column = location.getColumnNumber();
675 this.charOffset = location.getCharacterOffset();
676 }
677
678 @Override
679 public int getLine() {
680 return line;
681 }
682
683 @Override
684 public int getColumn() {
685 return column;
686 }
687
688 @Override
689 public long getCharOffset() {
690 return charOffset;
691 }
692
693 @Override
694 public long getByteOffset() {
695 return -1;
696 }
697 }
698
699 @FunctionalInterface
700 private interface DefinitionBodyHandler<DEF extends IBoundDefinitionModelComplex, ITEM> {
701 void accept(
702 @NonNull DEF definition,
703 @NonNull ITEM item) throws IOException;
704 }
705
706 }