1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package gov.nist.secauto.metaschema.databind.io.xml;
28
29 import gov.nist.secauto.metaschema.core.datatype.IDataTypeAdapter;
30 import gov.nist.secauto.metaschema.core.model.util.XmlEventUtil;
31 import gov.nist.secauto.metaschema.core.util.CollectionUtil;
32 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
33 import gov.nist.secauto.metaschema.databind.io.BindingException;
34 import gov.nist.secauto.metaschema.databind.model.IAssemblyClassBinding;
35 import gov.nist.secauto.metaschema.databind.model.IBoundAssemblyInstance;
36 import gov.nist.secauto.metaschema.databind.model.IBoundFieldInstance;
37 import gov.nist.secauto.metaschema.databind.model.IBoundFieldValueInstance;
38 import gov.nist.secauto.metaschema.databind.model.IBoundFlagInstance;
39 import gov.nist.secauto.metaschema.databind.model.IBoundNamedModelInstance;
40 import gov.nist.secauto.metaschema.databind.model.IClassBinding;
41 import gov.nist.secauto.metaschema.databind.model.IFieldClassBinding;
42 import gov.nist.secauto.metaschema.databind.model.info.IPropertyCollector;
43
44 import org.codehaus.stax2.XMLEventReader2;
45
46 import java.io.IOException;
47 import java.util.HashSet;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.function.Function;
51 import java.util.stream.Collectors;
52
53 import javax.xml.namespace.QName;
54 import javax.xml.stream.XMLStreamConstants;
55 import javax.xml.stream.XMLStreamException;
56 import javax.xml.stream.events.Attribute;
57 import javax.xml.stream.events.StartElement;
58 import javax.xml.stream.events.XMLEvent;
59
60 import edu.umd.cs.findbugs.annotations.NonNull;
61 import edu.umd.cs.findbugs.annotations.Nullable;
62
63 public class MetaschemaXmlReader
64 implements IXmlParsingContext {
65 @NonNull
66 private final XMLEventReader2 reader;
67 @NonNull
68 private final IXmlProblemHandler problemHandler;
69
70
71
72
73
74
75
76
77 public MetaschemaXmlReader(
78 @NonNull XMLEventReader2 reader) {
79 this(reader, new DefaultXmlProblemHandler());
80 }
81
82
83
84
85
86
87
88
89
90 public MetaschemaXmlReader(
91 @NonNull XMLEventReader2 reader,
92 @NonNull IXmlProblemHandler problemHandler) {
93 this.reader = reader;
94 this.problemHandler = problemHandler;
95 }
96
97 @Override
98 public XMLEventReader2 getReader() {
99 return reader;
100 }
101
102 @Override
103 public IXmlProblemHandler getProblemHandler() {
104 return problemHandler;
105 }
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 @NonNull
124 public <CLASS> CLASS read(@NonNull IAssemblyClassBinding targetDefinition) throws IOException, XMLStreamException {
125
126
127 if (reader.peek().isStartDocument()) {
128 XmlEventUtil.consumeAndAssert(reader, XMLStreamConstants.START_DOCUMENT);
129 }
130
131 XmlEventUtil.skipEvents(reader, XMLStreamConstants.CHARACTERS, XMLStreamConstants.PROCESSING_INSTRUCTION);
132
133 QName rootQName = targetDefinition.getRootXmlQName();
134 XMLEvent event = XmlEventUtil.consumeAndAssert(reader, XMLStreamConstants.START_ELEMENT, rootQName);
135
136 StartElement start = ObjectUtils.notNull(event.asStartElement());
137
138 CLASS retval = readDefinitionValue(targetDefinition, null, start);
139
140 XmlEventUtil.consumeAndAssert(reader, XMLStreamConstants.END_ELEMENT, rootQName);
141
142
143
144
145
146 return retval;
147 }
148
149 @SuppressWarnings("PMD.CyclomaticComplexity")
150 @Override
151 public <T> T readDefinitionValue(
152 IClassBinding targetDefinition,
153 Object parentObject,
154 StartElement start) throws IOException, XMLStreamException {
155
156 Object targetObject;
157 try {
158 targetObject = targetDefinition.newInstance();
159 targetDefinition.callBeforeDeserialize(targetObject, parentObject);
160 } catch (BindingException ex) {
161 throw new IOException(ex);
162 }
163
164 readFlagInstances(targetDefinition, targetObject, start);
165
166 if (targetDefinition instanceof IAssemblyClassBinding) {
167 readModelInstances((IAssemblyClassBinding) targetDefinition, targetObject, start);
168 } else if (targetDefinition instanceof IFieldClassBinding) {
169 readFieldValue((IFieldClassBinding) targetDefinition, targetObject);
170 } else {
171 throw new UnsupportedOperationException(
172 String.format("Unsupported class binding type: %s", targetDefinition.getClass().getName()));
173 }
174
175 XmlEventUtil.skipWhitespace(reader);
176
177 XMLEvent nextEvent = ObjectUtils.notNull(reader.peek());
178 if (!XmlEventUtil.isEventEndElement(nextEvent, ObjectUtils.notNull(start.getName()))) {
179 throw new IOException(
180 String.format("Unrecognized element '%s'%s.",
181 XmlEventUtil.toEventName(nextEvent),
182 XmlEventUtil.generateLocationMessage(nextEvent)));
183 }
184
185 try {
186 targetDefinition.callAfterDeserialize(targetObject, parentObject);
187 } catch (BindingException ex) {
188 throw new IOException(ex);
189 }
190 return ObjectUtils.asType(targetObject);
191 }
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 protected void readFlagInstances(
209 @NonNull IClassBinding targetDefinition,
210 @NonNull Object targetObject,
211 @NonNull StartElement start) throws IOException, XMLStreamException {
212
213 Map<QName, IBoundFlagInstance> flagInstanceMap = targetDefinition.getFlagInstances().stream()
214 .collect(Collectors.toMap(IBoundFlagInstance::getXmlQName, Function.identity()));
215
216 for (Attribute attribute : CollectionUtil.toIterable(ObjectUtils.notNull(start.getAttributes()))) {
217 QName qname = attribute.getName();
218 IBoundFlagInstance instance = flagInstanceMap.get(qname);
219 if (instance == null) {
220
221 if (!getProblemHandler().handleUnknownAttribute(targetDefinition, targetObject, attribute, this)) {
222 throw new IOException(
223 String.format("Unrecognized attribute '%s'%s.",
224 qname,
225 XmlEventUtil.generateLocationMessage(attribute)));
226 }
227 } else {
228
229 Object value = instance.getDefinition().getJavaTypeAdapter().parse(ObjectUtils.notNull(attribute.getValue()));
230
231 instance.setValue(targetObject, value);
232 flagInstanceMap.remove(qname);
233 }
234 }
235
236 if (!flagInstanceMap.isEmpty()) {
237 getProblemHandler().handleMissingFlagInstances(
238 targetDefinition,
239 targetObject,
240 ObjectUtils.notNull(flagInstanceMap.values()));
241 }
242 }
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259 protected void readModelInstances(
260 @NonNull IAssemblyClassBinding targetDefinition,
261 @NonNull Object targetObject,
262 @NonNull StartElement start)
263 throws IOException, XMLStreamException {
264 Set<IBoundNamedModelInstance> unhandledProperties = new HashSet<>();
265 for (IBoundNamedModelInstance modelProperty : targetDefinition.getModelInstances()) {
266 assert modelProperty != null;
267 if (!readModelInstanceValues(modelProperty, targetObject, start)) {
268 unhandledProperties.add(modelProperty);
269 }
270 }
271
272
273 getProblemHandler().handleMissingModelInstances(targetDefinition, targetObject, unhandledProperties);
274 }
275
276
277
278
279
280
281
282
283
284
285
286
287 protected void readFieldValue(
288 @NonNull IFieldClassBinding targetDefinition,
289 @NonNull Object targetObject)
290 throws IOException {
291 IBoundFieldValueInstance fieldValue = targetDefinition.getFieldValueInstance();
292
293
294 Object value = fieldValue.getJavaTypeAdapter().parse(reader);
295 fieldValue.setValue(targetObject, value);
296 }
297
298
299
300
301
302
303
304
305
306
307
308 @SuppressWarnings("PMD.OnlyOneReturn")
309 protected boolean isNextInstance(
310 @NonNull IBoundNamedModelInstance targetInstance)
311 throws XMLStreamException {
312
313 XmlEventUtil.skipWhitespace(reader);
314
315 XMLEvent nextEvent = reader.peek();
316 if (!nextEvent.isStartElement()) {
317 return false;
318 }
319
320 QName nextQName = ObjectUtils.notNull(nextEvent.asStartElement().getName());
321
322 if (nextQName.equals(targetInstance.getXmlGroupAsQName())) {
323
324 return true;
325 }
326
327 if (nextQName.equals(targetInstance.getXmlQName())) {
328
329 return true;
330 }
331
332 if (targetInstance instanceof IBoundFieldInstance) {
333 IBoundFieldInstance fieldInstance = (IBoundFieldInstance) targetInstance;
334 IDataTypeAdapter<?> adapter = fieldInstance.getDefinition().getJavaTypeAdapter();
335
336 return !fieldInstance.isInXmlWrapped()
337 && adapter.isUnrappedValueAllowedInXml()
338 && adapter.canHandleQName(nextQName);
339 }
340 return false;
341 }
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360 protected boolean readModelInstanceValues(
361 @NonNull IBoundNamedModelInstance instance,
362 @NonNull Object parentObject,
363 @NonNull StartElement start)
364 throws IOException, XMLStreamException {
365 boolean handled = isNextInstance(instance);
366 if (handled) {
367 XmlEventUtil.skipWhitespace(reader);
368
369 StartElement currentStart = start;
370
371 QName groupQName = instance.getXmlGroupAsQName();
372 if (groupQName != null) {
373
374 XMLEvent groupEvent = XmlEventUtil.consumeAndAssert(reader, XMLStreamConstants.START_ELEMENT, groupQName);
375 currentStart = ObjectUtils.notNull(groupEvent.asStartElement());
376 }
377
378 IPropertyCollector collector = instance.getPropertyInfo().newPropertyCollector();
379
380 instance.getPropertyInfo().readValues(collector, parentObject, currentStart, this);
381
382 Object value = collector.getValue();
383
384
385 XmlEventUtil.skipWhitespace(reader);
386
387 if (groupQName != null) {
388
389 XmlEventUtil.consumeAndAssert(reader, XMLStreamConstants.END_ELEMENT, groupQName);
390 }
391
392 instance.setValue(parentObject, value);
393 }
394 return handled;
395 }
396
397 @Override
398 public <T> T readModelInstanceValue(IBoundNamedModelInstance instance, Object parentObject, StartElement start)
399 throws XMLStreamException, IOException {
400 Object retval;
401 if (instance instanceof IBoundAssemblyInstance) {
402 retval = readModelInstanceValue((IBoundAssemblyInstance) instance, parentObject, start);
403 } else if (instance instanceof IBoundFieldInstance) {
404 retval = readModelInstanceValue((IBoundFieldInstance) instance, parentObject, start);
405 } else {
406 throw new UnsupportedOperationException(
407 String.format("Unsupported instance type: %s", instance.getClass().getName()));
408 }
409 return ObjectUtils.asNullableType(retval);
410 }
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428 @Nullable
429 protected Object readModelInstanceValue(
430 @NonNull IBoundAssemblyInstance instance,
431 @NonNull Object parentObject,
432 @NonNull StartElement start) throws XMLStreamException, IOException {
433
434 XmlEventUtil.skipWhitespace(reader);
435
436 Object retval = null;
437 XMLEvent event = reader.peek();
438 if (event.isStartElement()) {
439 StartElement nextStart = event.asStartElement();
440 QName nextQName = nextStart.getName();
441 if (instance.getXmlQName().equals(nextQName)) {
442
443 reader.nextEvent();
444
445
446 retval = instance.getDataTypeHandler().readItem(parentObject, nextStart, this);
447
448
449 XmlEventUtil.consumeAndAssert(reader, XMLStreamConstants.END_ELEMENT, nextQName);
450 }
451 }
452 return retval;
453 }
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472 @NonNull
473 protected Object readModelInstanceValue(
474 @NonNull IBoundFieldInstance instance,
475 @NonNull Object parentObject,
476 @NonNull StartElement start) throws XMLStreamException, IOException {
477
478 IDataTypeAdapter<?> adapter = instance.getDefinition().getJavaTypeAdapter();
479 boolean parseWrapper = true;
480 if (!instance.isInXmlWrapped() && adapter.isUnrappedValueAllowedInXml()) {
481 parseWrapper = false;
482 }
483
484 StartElement currentStart = start;
485 if (parseWrapper) {
486
487
488 XmlEventUtil.skipWhitespace(reader);
489
490 QName xmlQName = instance.getXmlQName();
491 XMLEvent event = reader.peek();
492 if (event.isStartElement() && xmlQName.equals(event.asStartElement().getName())) {
493
494 currentStart = ObjectUtils.notNull(reader.nextEvent().asStartElement());
495 } else {
496 throw new IOException(String.format("Found '%s' instead of expected element '%s'%s.",
497 event.asStartElement().getName(),
498 xmlQName,
499 XmlEventUtil.generateLocationMessage(event)));
500 }
501 }
502
503
504 Object retval = instance.getDataTypeHandler().readItem(parentObject, currentStart, this);
505
506 if (parseWrapper) {
507
508 XmlEventUtil.consumeAndAssert(reader, XMLStreamConstants.END_ELEMENT, currentStart.getName());
509 }
510
511 return retval;
512 }
513 }