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