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