1
2
3
4
5
6 package dev.metaschema.databind.io.xml;
7
8 import org.codehaus.stax2.XMLStreamWriter2;
9 import org.w3c.dom.Element;
10
11 import java.io.IOException;
12 import java.util.List;
13
14 import javax.xml.namespace.NamespaceContext;
15 import javax.xml.stream.XMLStreamException;
16
17 import dev.metaschema.core.model.IAnyContent;
18 import dev.metaschema.core.model.IAnyInstance;
19 import dev.metaschema.core.model.IBoundObject;
20 import dev.metaschema.core.qname.IEnhancedQName;
21 import dev.metaschema.databind.io.json.DefaultJsonProblemHandler;
22 import dev.metaschema.databind.model.IBoundDefinitionModel;
23 import dev.metaschema.databind.model.IBoundDefinitionModelAssembly;
24 import dev.metaschema.databind.model.IBoundDefinitionModelComplex;
25 import dev.metaschema.databind.model.IBoundDefinitionModelFieldComplex;
26 import dev.metaschema.databind.model.IBoundFieldValue;
27 import dev.metaschema.databind.model.IBoundInstanceFlag;
28 import dev.metaschema.databind.model.IBoundInstanceModel;
29 import dev.metaschema.databind.model.IBoundInstanceModelAny;
30 import dev.metaschema.databind.model.IBoundInstanceModelAssembly;
31 import dev.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
32 import dev.metaschema.databind.model.IBoundInstanceModelFieldComplex;
33 import dev.metaschema.databind.model.IBoundInstanceModelFieldScalar;
34 import dev.metaschema.databind.model.IBoundInstanceModelGroupedAssembly;
35 import dev.metaschema.databind.model.IBoundInstanceModelGroupedField;
36 import dev.metaschema.databind.model.IBoundInstanceModelGroupedNamed;
37 import dev.metaschema.databind.model.IBoundInstanceModelNamed;
38 import dev.metaschema.databind.model.info.AbstractModelInstanceWriteHandler;
39 import dev.metaschema.databind.model.info.IFeatureComplexItemValueHandler;
40 import dev.metaschema.databind.model.info.IItemWriteHandler;
41 import dev.metaschema.databind.model.info.IModelInstanceCollectionInfo;
42 import edu.umd.cs.findbugs.annotations.NonNull;
43
44
45
46
47
48
49
50
51
52
53
54
55 public class MetaschemaXmlWriter implements IXmlWritingContext {
56 @NonNull
57 private final XMLStreamWriter2 writer;
58
59
60
61
62
63
64
65
66 public MetaschemaXmlWriter(
67 @NonNull XMLStreamWriter2 writer) {
68 this.writer = writer;
69 }
70
71 @Override
72 public XMLStreamWriter2 getWriter() {
73 return writer;
74 }
75
76
77
78
79
80 @Override
81 public void write(
82 @NonNull IBoundDefinitionModelComplex definition,
83 @NonNull IBoundObject item) throws IOException {
84
85 IEnhancedQName qname = definition.getQName();
86
87 definition.writeItem(item, new ItemWriter(qname));
88 }
89
90 @Override
91 public void writeRoot(
92 @NonNull IBoundDefinitionModelAssembly definition,
93 @NonNull IBoundObject item) throws IOException {
94 IEnhancedQName rootEQName = definition.getRootQName();
95 if (rootEQName == null) {
96 throw new IllegalArgumentException(
97 String.format("The assembly definition '%s' does not have a root QName.",
98 definition.getQName()));
99 }
100
101 definition.writeItem(item, new ItemWriter(rootEQName));
102 }
103
104
105
106
107
108 private <T> void writeModelInstance(
109 @NonNull IBoundInstanceModel<T> instance,
110 @NonNull Object parentItem,
111 @NonNull ItemWriter itemWriter) throws IOException {
112 Object value = instance.getValue(parentItem);
113 if (value == null) {
114 return;
115 }
116
117
118
119
120
121 IModelInstanceCollectionInfo<T> collectionInfo = instance.getCollectionInfo();
122 if (!collectionInfo.isEmpty(value)) {
123 IEnhancedQName currentQName = itemWriter.getObjectQName();
124 IEnhancedQName groupAsEQName = instance.getEffectiveXmlGroupAsQName();
125 try {
126 if (groupAsEQName != null) {
127
128 writer.writeStartElement(groupAsEQName.getNamespace(), groupAsEQName.getLocalName());
129 currentQName = groupAsEQName;
130 }
131
132 collectionInfo.writeItems(
133 new ModelInstanceWriteHandler<>(instance, new ItemWriter(currentQName)),
134 value);
135
136 if (groupAsEQName != null) {
137 writer.writeEndElement();
138 }
139 } catch (XMLStreamException ex) {
140 throw new IOException(ex);
141 }
142 }
143 }
144
145 private static class ModelInstanceWriteHandler<ITEM>
146 extends AbstractModelInstanceWriteHandler<ITEM> {
147 @NonNull
148 private final ItemWriter itemWriter;
149
150 public ModelInstanceWriteHandler(
151 @NonNull IBoundInstanceModel<ITEM> instance,
152 @NonNull ItemWriter itemWriter) {
153 super(instance);
154 this.itemWriter = itemWriter;
155 }
156
157 @Override
158 public void writeItem(ITEM item) throws IOException {
159 IBoundInstanceModel<ITEM> instance = getInstance();
160 instance.writeItem(item, itemWriter);
161 }
162 }
163
164 private class ItemWriter
165 extends AbstractItemWriter {
166
167 public ItemWriter(@NonNull IEnhancedQName qname) {
168 super(qname);
169 }
170
171 private <T extends IBoundInstanceModelNamed<IBoundObject> & IFeatureComplexItemValueHandler> void writeFlags(
172 @NonNull IBoundObject parentItem,
173 @NonNull T instance) throws IOException {
174 writeFlags(parentItem, instance.getDefinition());
175 }
176
177 private <T extends IBoundInstanceModelGroupedNamed & IFeatureComplexItemValueHandler> void writeFlags(
178 @NonNull IBoundObject parentItem,
179 @NonNull T instance) throws IOException {
180 writeFlags(parentItem, instance.getDefinition());
181 }
182
183 private void writeFlags(
184 @NonNull IBoundObject parentItem,
185 @NonNull IBoundDefinitionModel<?> definition) throws IOException {
186 for (IBoundInstanceFlag flag : definition.getFlagInstances()) {
187 assert flag != null;
188
189 Object value = flag.getValue(parentItem);
190 if (value != null) {
191 writeItemFlag(value, flag);
192 }
193 }
194 }
195
196 private <T extends IBoundInstanceModelAssembly & IFeatureComplexItemValueHandler> void writeAssemblyModel(
197 @NonNull IBoundObject parentItem,
198 @NonNull T instance) throws IOException {
199 writeAssemblyModel(parentItem, instance.getDefinition());
200 }
201
202 private <T extends IBoundInstanceModelGroupedAssembly & IFeatureComplexItemValueHandler> void writeAssemblyModel(
203 @NonNull IBoundObject parentItem,
204 @NonNull T instance) throws IOException {
205 writeAssemblyModel(parentItem, instance.getDefinition());
206 }
207
208 private void writeAssemblyModel(
209 @NonNull IBoundObject parentItem,
210 @NonNull IBoundDefinitionModelAssembly definition) throws IOException {
211 for (IBoundInstanceModel<?> modelInstance : definition.getModelInstances()) {
212 assert modelInstance != null;
213 writeModelInstance(modelInstance, parentItem, this);
214 }
215
216
217 IAnyInstance anyInstance = definition.getModelContainer().getAnyInstance();
218 if (anyInstance instanceof IBoundInstanceModelAny) {
219 IBoundInstanceModelAny boundAny = (IBoundInstanceModelAny) anyInstance;
220 IAnyContent anyContent = boundAny.getAnyContent(parentItem);
221 if (anyContent instanceof XmlAnyContent) {
222 XmlAnyContent xmlAnyContent = (XmlAnyContent) anyContent;
223 if (!xmlAnyContent.isEmpty()) {
224 try {
225 List<Element> elements = xmlAnyContent.getElements();
226 for (Element element : elements) {
227 XmlDomUtil.elementToStax(element, writer);
228 }
229 } catch (XMLStreamException ex) {
230 throw new IOException(ex);
231 }
232 }
233 }
234 }
235 }
236
237 private void writeFieldValue(
238 @NonNull IBoundObject parentItem,
239 @NonNull IBoundInstanceModelFieldComplex instance) throws IOException {
240 writeFieldValue(parentItem, instance.getDefinition());
241 }
242
243 private void writeFieldValue(
244 @NonNull IBoundObject parentItem,
245 @NonNull IBoundInstanceModelGroupedField instance) throws IOException {
246 writeFieldValue(parentItem, instance.getDefinition());
247 }
248
249 private void writeFieldValue(
250 @NonNull IBoundObject parentItem,
251 @NonNull IBoundDefinitionModelFieldComplex definition) throws IOException {
252 definition.getFieldValue().writeItem(parentItem, this);
253 }
254
255 private <T extends IFeatureComplexItemValueHandler & IBoundInstanceModelNamed<IBoundObject>> void writeModelObject(
256 @NonNull T instance,
257 @NonNull IBoundObject parentItem,
258 @NonNull ObjectWriter<T> propertyWriter) throws IOException {
259 try {
260 IEnhancedQName wrapperQName = instance.getQName();
261 writer.writeStartElement(wrapperQName.getNamespace(), wrapperQName.getLocalName());
262
263 propertyWriter.accept(parentItem, instance);
264
265 writer.writeEndElement();
266 } catch (XMLStreamException ex) {
267 throw new IOException(ex);
268 }
269 }
270
271 private <T extends IFeatureComplexItemValueHandler & IBoundInstanceModelGroupedNamed> void writeGroupedModelObject(
272 @NonNull T instance,
273 @NonNull IBoundObject parentItem,
274 @NonNull ObjectWriter<T> propertyWriter) throws IOException {
275 try {
276 IEnhancedQName wrapperQName = instance.getQName();
277 writer.writeStartElement(wrapperQName.getNamespace(), wrapperQName.getLocalName());
278
279 propertyWriter.accept(parentItem, instance);
280
281 writer.writeEndElement();
282 } catch (XMLStreamException ex) {
283 throw new IOException(ex);
284 }
285 }
286
287 private <T extends IFeatureComplexItemValueHandler & IBoundDefinitionModelComplex> void writeDefinitionObject(
288 @NonNull T definition,
289 @NonNull IBoundObject parentItem,
290 @NonNull ObjectWriter<T> propertyWriter) throws IOException {
291
292 try {
293 IEnhancedQName qname = getObjectQName();
294 NamespaceContext nsContext = writer.getNamespaceContext();
295 String prefix = nsContext.getPrefix(qname.getNamespace());
296 if (prefix == null) {
297 prefix = "";
298 }
299
300 writer.writeStartElement(prefix, qname.getLocalName(), qname.getNamespace());
301
302 propertyWriter.accept(parentItem, definition);
303
304 writer.writeEndElement();
305 } catch (XMLStreamException ex) {
306 throw new IOException(ex);
307 }
308 }
309
310 @Override
311 public void writeItemFlag(Object item, IBoundInstanceFlag instance) throws IOException {
312 String itemString;
313 try {
314 itemString = instance.getJavaTypeAdapter().asString(item);
315 } catch (IllegalArgumentException ex) {
316 throw new IOException(ex);
317 }
318 IEnhancedQName name = instance.getQName();
319 try {
320 if (name.getNamespace().isEmpty()) {
321 writer.writeAttribute(name.getLocalName(), itemString);
322 } else {
323 writer.writeAttribute(name.getNamespace(), name.getLocalName(), itemString);
324 }
325 } catch (XMLStreamException ex) {
326 throw new IOException(ex);
327 }
328 }
329
330 @Override
331 public void writeItemField(Object item, IBoundInstanceModelFieldScalar instance) throws IOException {
332 try {
333 if (instance.isEffectiveValueWrappedInXml()) {
334 IEnhancedQName wrapperQName = instance.getQName();
335 writer.writeStartElement(wrapperQName.getNamespace(), wrapperQName.getLocalName());
336 instance.getJavaTypeAdapter().writeXmlValue(item, wrapperQName, writer);
337 writer.writeEndElement();
338 } else {
339 instance.getJavaTypeAdapter().writeXmlValue(item, getObjectQName(), writer);
340 }
341 } catch (XMLStreamException ex) {
342 throw new IOException(ex);
343 }
344 }
345
346 @Override
347 public void writeItemField(IBoundObject item, IBoundInstanceModelFieldComplex instance) throws IOException {
348 ItemWriter itemWriter = new ItemWriter(instance.getQName());
349 writeModelObject(
350 instance,
351 item,
352 ((ObjectWriter<IBoundInstanceModelFieldComplex>) this::writeFlags)
353 .andThen(itemWriter::writeFieldValue));
354 }
355
356 @Override
357 public void writeItemField(IBoundObject item, IBoundInstanceModelGroupedField instance) throws IOException {
358 ItemWriter itemWriter = new ItemWriter(instance.getQName());
359 writeGroupedModelObject(
360 instance,
361 item,
362 ((ObjectWriter<IBoundInstanceModelGroupedField>) this::writeFlags)
363 .andThen(itemWriter::writeFieldValue));
364 }
365
366 @Override
367 public void writeItemField(IBoundObject item, IBoundDefinitionModelFieldComplex definition) throws IOException {
368 ItemWriter itemWriter = new ItemWriter(definition.getQName());
369 writeDefinitionObject(
370 definition,
371 item,
372 ((ObjectWriter<IBoundDefinitionModelFieldComplex>) this::writeFlags)
373 .andThen(itemWriter::writeFieldValue));
374 }
375
376 @Override
377 public void writeItemFieldValue(Object parentItem, IBoundFieldValue fieldValue) throws IOException {
378 Object item = fieldValue.getValue(parentItem);
379 if (item != null) {
380 fieldValue.getJavaTypeAdapter().writeXmlValue(item, getObjectQName(), writer);
381 }
382 }
383
384 @Override
385 public void writeItemAssembly(IBoundObject item, IBoundInstanceModelAssembly instance) throws IOException {
386 ItemWriter itemWriter = new ItemWriter(instance.getQName());
387 writeModelObject(
388 instance,
389 item,
390 ((ObjectWriter<IBoundInstanceModelAssembly>) this::writeFlags)
391 .andThen(itemWriter::writeAssemblyModel));
392 }
393
394 @Override
395 public void writeItemAssembly(IBoundObject item, IBoundInstanceModelGroupedAssembly instance) throws IOException {
396 ItemWriter itemWriter = new ItemWriter(instance.getQName());
397 writeGroupedModelObject(
398 instance,
399 item,
400 ((ObjectWriter<IBoundInstanceModelGroupedAssembly>) this::writeFlags)
401 .andThen(itemWriter::writeAssemblyModel));
402 }
403
404 @Override
405 public void writeItemAssembly(IBoundObject item, IBoundDefinitionModelAssembly definition) throws IOException {
406
407
408 writeDefinitionObject(
409 definition,
410 item,
411 ((ObjectWriter<IBoundDefinitionModelAssembly>) this::writeFlags)
412 .andThen(this::writeAssemblyModel));
413 }
414
415 @Override
416 public void writeChoiceGroupItem(IBoundObject item, IBoundInstanceModelChoiceGroup instance) throws IOException {
417 IBoundInstanceModelGroupedNamed actualInstance = instance.getItemInstance(item);
418 assert actualInstance != null;
419 actualInstance.writeItem(item, this);
420 }
421 }
422
423 private abstract static class AbstractItemWriter implements IItemWriteHandler {
424 @NonNull
425 private final IEnhancedQName objectQName;
426
427 protected AbstractItemWriter(@NonNull IEnhancedQName qname) {
428 this.objectQName = qname;
429 }
430
431
432
433
434
435
436 @NonNull
437 protected IEnhancedQName getObjectQName() {
438 return objectQName;
439 }
440 }
441 }