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