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