001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.databind.io.xml;
007
008import gov.nist.secauto.metaschema.core.model.IBoundObject;
009import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
010import gov.nist.secauto.metaschema.databind.io.json.DefaultJsonProblemHandler;
011import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModel;
012import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
013import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelComplex;
014import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelFieldComplex;
015import gov.nist.secauto.metaschema.databind.model.IBoundFieldValue;
016import gov.nist.secauto.metaschema.databind.model.IBoundInstanceFlag;
017import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel;
018import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelAssembly;
019import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelChoiceGroup;
020import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldComplex;
021import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelFieldScalar;
022import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedAssembly;
023import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedField;
024import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelGroupedNamed;
025import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModelNamed;
026import gov.nist.secauto.metaschema.databind.model.info.AbstractModelInstanceWriteHandler;
027import gov.nist.secauto.metaschema.databind.model.info.IFeatureComplexItemValueHandler;
028import gov.nist.secauto.metaschema.databind.model.info.IItemWriteHandler;
029import gov.nist.secauto.metaschema.databind.model.info.IModelInstanceCollectionInfo;
030
031import org.codehaus.stax2.XMLStreamWriter2;
032
033import java.io.IOException;
034
035import javax.xml.namespace.NamespaceContext;
036import javax.xml.stream.XMLStreamException;
037
038import edu.umd.cs.findbugs.annotations.NonNull;
039
040public class MetaschemaXmlWriter implements IXmlWritingContext {
041  @NonNull
042  private final XMLStreamWriter2 writer;
043
044  /**
045   * Construct a new Module-aware JSON writer.
046   *
047   * @param writer
048   *          the XML stream writer to write with
049   * @see DefaultJsonProblemHandler
050   */
051  public MetaschemaXmlWriter(
052      @NonNull XMLStreamWriter2 writer) {
053    this.writer = writer;
054  }
055
056  @Override
057  public XMLStreamWriter2 getWriter() {
058    return writer;
059  }
060
061  // =====================================
062  // Entry point for top-level-definitions
063  // =====================================
064
065  @Override
066  public void write(
067      @NonNull IBoundDefinitionModelComplex definition,
068      @NonNull IBoundObject item) throws IOException {
069
070    IEnhancedQName qname = definition.getQName();
071
072    definition.writeItem(item, new ItemWriter(qname));
073  }
074
075  @Override
076  public void writeRoot(
077      @NonNull IBoundDefinitionModelAssembly definition,
078      @NonNull IBoundObject item) throws IOException {
079    IEnhancedQName rootEQName = definition.getRootQName();
080    if (rootEQName == null) {
081      throw new IllegalArgumentException(
082          String.format("The assembly definition '%s' does not have a root QName.",
083              definition.getQName()));
084    }
085
086    definition.writeItem(item, new ItemWriter(rootEQName));
087  }
088
089  // ================
090  // Instance writers
091  // ================
092
093  private <T> void writeModelInstance(
094      @NonNull IBoundInstanceModel<T> instance,
095      @NonNull Object parentItem,
096      @NonNull ItemWriter itemWriter) throws IOException {
097    Object value = instance.getValue(parentItem);
098    if (value == null) {
099      return;
100    }
101
102    // this if is not strictly needed, since isEmpty will return false on a null
103    // value
104    // checking null here potentially avoids the expensive operation of
105    // instantiating
106    IModelInstanceCollectionInfo<T> collectionInfo = instance.getCollectionInfo();
107    if (!collectionInfo.isEmpty(value)) {
108      IEnhancedQName currentQName = itemWriter.getObjectQName();
109      IEnhancedQName groupAsEQName = instance.getEffectiveXmlGroupAsQName();
110      try {
111        if (groupAsEQName != null) {
112          // write the grouping element
113          writer.writeStartElement(groupAsEQName.getNamespace(), groupAsEQName.getLocalName());
114          currentQName = groupAsEQName;
115        }
116
117        collectionInfo.writeItems(
118            new ModelInstanceWriteHandler<>(instance, new ItemWriter(currentQName)),
119            value);
120
121        if (groupAsEQName != null) {
122          writer.writeEndElement();
123        }
124      } catch (XMLStreamException ex) {
125        throw new IOException(ex);
126      }
127    }
128  }
129
130  private static class ModelInstanceWriteHandler<ITEM>
131      extends AbstractModelInstanceWriteHandler<ITEM> {
132    @NonNull
133    private final ItemWriter itemWriter;
134
135    public ModelInstanceWriteHandler(
136        @NonNull IBoundInstanceModel<ITEM> instance,
137        @NonNull ItemWriter itemWriter) {
138      super(instance);
139      this.itemWriter = itemWriter;
140    }
141
142    @Override
143    public void writeItem(ITEM item) throws IOException {
144      IBoundInstanceModel<ITEM> instance = getInstance();
145      instance.writeItem(item, itemWriter);
146    }
147  }
148
149  private class ItemWriter
150      extends AbstractItemWriter {
151
152    public ItemWriter(@NonNull IEnhancedQName qname) {
153      super(qname);
154    }
155
156    private <T extends IBoundInstanceModelNamed<IBoundObject> & IFeatureComplexItemValueHandler> void writeFlags(
157        @NonNull IBoundObject parentItem,
158        @NonNull T instance) throws IOException {
159      writeFlags(parentItem, instance.getDefinition());
160    }
161
162    private <T extends IBoundInstanceModelGroupedNamed & IFeatureComplexItemValueHandler> void writeFlags(
163        @NonNull IBoundObject parentItem,
164        @NonNull T instance) throws IOException {
165      writeFlags(parentItem, instance.getDefinition());
166    }
167
168    private void writeFlags(
169        @NonNull IBoundObject parentItem,
170        @NonNull IBoundDefinitionModel<?> definition) throws IOException {
171      for (IBoundInstanceFlag flag : definition.getFlagInstances()) {
172        assert flag != null;
173
174        Object value = flag.getValue(parentItem);
175        if (value != null) {
176          writeItemFlag(value, flag);
177        }
178      }
179    }
180
181    private <T extends IBoundInstanceModelAssembly & IFeatureComplexItemValueHandler> void writeAssemblyModel(
182        @NonNull IBoundObject parentItem,
183        @NonNull T instance) throws IOException {
184      writeAssemblyModel(parentItem, instance.getDefinition());
185    }
186
187    private <T extends IBoundInstanceModelGroupedAssembly & IFeatureComplexItemValueHandler> void writeAssemblyModel(
188        @NonNull IBoundObject parentItem,
189        @NonNull T instance) throws IOException {
190      writeAssemblyModel(parentItem, instance.getDefinition());
191    }
192
193    private void writeAssemblyModel(
194        @NonNull IBoundObject parentItem,
195        @NonNull IBoundDefinitionModelAssembly definition) throws IOException {
196      for (IBoundInstanceModel<?> modelInstance : definition.getModelInstances()) {
197        assert modelInstance != null;
198        writeModelInstance(modelInstance, parentItem, this);
199      }
200    }
201
202    private void writeFieldValue(
203        @NonNull IBoundObject parentItem,
204        @NonNull IBoundInstanceModelFieldComplex instance) throws IOException {
205      writeFieldValue(parentItem, instance.getDefinition());
206    }
207
208    private void writeFieldValue(
209        @NonNull IBoundObject parentItem,
210        @NonNull IBoundInstanceModelGroupedField instance) throws IOException {
211      writeFieldValue(parentItem, instance.getDefinition());
212    }
213
214    private void writeFieldValue(
215        @NonNull IBoundObject parentItem,
216        @NonNull IBoundDefinitionModelFieldComplex definition) throws IOException {
217      definition.getFieldValue().writeItem(parentItem, this);
218    }
219
220    private <T extends IFeatureComplexItemValueHandler & IBoundInstanceModelNamed<IBoundObject>> void writeModelObject(
221        @NonNull T instance,
222        @NonNull IBoundObject parentItem,
223        @NonNull ObjectWriter<T> propertyWriter) throws IOException {
224      try {
225        IEnhancedQName wrapperQName = instance.getQName();
226        writer.writeStartElement(wrapperQName.getNamespace(), wrapperQName.getLocalName());
227
228        propertyWriter.accept(parentItem, instance);
229
230        writer.writeEndElement();
231      } catch (XMLStreamException ex) {
232        throw new IOException(ex);
233      }
234    }
235
236    private <T extends IFeatureComplexItemValueHandler & IBoundInstanceModelGroupedNamed> void writeGroupedModelObject(
237        @NonNull T instance,
238        @NonNull IBoundObject parentItem,
239        @NonNull ObjectWriter<T> propertyWriter) throws IOException {
240      try {
241        IEnhancedQName wrapperQName = instance.getQName();
242        writer.writeStartElement(wrapperQName.getNamespace(), wrapperQName.getLocalName());
243
244        propertyWriter.accept(parentItem, instance);
245
246        writer.writeEndElement();
247      } catch (XMLStreamException ex) {
248        throw new IOException(ex);
249      }
250    }
251
252    private <T extends IFeatureComplexItemValueHandler & IBoundDefinitionModelComplex> void writeDefinitionObject(
253        @NonNull T definition,
254        @NonNull IBoundObject parentItem,
255        @NonNull ObjectWriter<T> propertyWriter) throws IOException {
256
257      try {
258        IEnhancedQName qname = getObjectQName();
259        NamespaceContext nsContext = writer.getNamespaceContext();
260        String prefix = nsContext.getPrefix(qname.getNamespace());
261        if (prefix == null) {
262          prefix = "";
263        }
264
265        writer.writeStartElement(prefix, qname.getLocalName(), qname.getNamespace());
266
267        propertyWriter.accept(parentItem, definition);
268
269        writer.writeEndElement();
270      } catch (XMLStreamException ex) {
271        throw new IOException(ex);
272      }
273    }
274
275    @Override
276    public void writeItemFlag(Object item, IBoundInstanceFlag instance) throws IOException {
277      String itemString;
278      try {
279        itemString = instance.getJavaTypeAdapter().asString(item);
280      } catch (IllegalArgumentException ex) {
281        throw new IOException(ex);
282      }
283      IEnhancedQName name = instance.getQName();
284      try {
285        if (name.getNamespace().isEmpty()) {
286          writer.writeAttribute(name.getLocalName(), itemString);
287        } else {
288          writer.writeAttribute(name.getNamespace(), name.getLocalName(), itemString);
289        }
290      } catch (XMLStreamException ex) {
291        throw new IOException(ex);
292      }
293    }
294
295    @Override
296    public void writeItemField(Object item, IBoundInstanceModelFieldScalar instance) throws IOException {
297      try {
298        if (instance.isEffectiveValueWrappedInXml()) {
299          IEnhancedQName wrapperQName = instance.getQName();
300          writer.writeStartElement(wrapperQName.getNamespace(), wrapperQName.getLocalName());
301          instance.getJavaTypeAdapter().writeXmlValue(item, wrapperQName, writer);
302          writer.writeEndElement();
303        } else {
304          instance.getJavaTypeAdapter().writeXmlValue(item, getObjectQName(), writer);
305        }
306      } catch (XMLStreamException ex) {
307        throw new IOException(ex);
308      }
309    }
310
311    @Override
312    public void writeItemField(IBoundObject item, IBoundInstanceModelFieldComplex instance) throws IOException {
313      ItemWriter itemWriter = new ItemWriter(instance.getQName());
314      writeModelObject(
315          instance,
316          item,
317          ((ObjectWriter<IBoundInstanceModelFieldComplex>) this::writeFlags)
318              .andThen(itemWriter::writeFieldValue));
319    }
320
321    @Override
322    public void writeItemField(IBoundObject item, IBoundInstanceModelGroupedField instance) throws IOException {
323      ItemWriter itemWriter = new ItemWriter(instance.getQName());
324      writeGroupedModelObject(
325          instance,
326          item,
327          ((ObjectWriter<IBoundInstanceModelGroupedField>) this::writeFlags)
328              .andThen(itemWriter::writeFieldValue));
329    }
330
331    @Override
332    public void writeItemField(IBoundObject item, IBoundDefinitionModelFieldComplex definition) throws IOException {
333      ItemWriter itemWriter = new ItemWriter(definition.getQName());
334      writeDefinitionObject(
335          definition,
336          item,
337          ((ObjectWriter<IBoundDefinitionModelFieldComplex>) this::writeFlags)
338              .andThen(itemWriter::writeFieldValue));
339    }
340
341    @Override
342    public void writeItemFieldValue(Object parentItem, IBoundFieldValue fieldValue) throws IOException {
343      Object item = fieldValue.getValue(parentItem);
344      if (item != null) {
345        fieldValue.getJavaTypeAdapter().writeXmlValue(item, getObjectQName(), writer);
346      }
347    }
348
349    @Override
350    public void writeItemAssembly(IBoundObject item, IBoundInstanceModelAssembly instance) throws IOException {
351      ItemWriter itemWriter = new ItemWriter(instance.getQName());
352      writeModelObject(
353          instance,
354          item,
355          ((ObjectWriter<IBoundInstanceModelAssembly>) this::writeFlags)
356              .andThen(itemWriter::writeAssemblyModel));
357    }
358
359    @Override
360    public void writeItemAssembly(IBoundObject item, IBoundInstanceModelGroupedAssembly instance) throws IOException {
361      ItemWriter itemWriter = new ItemWriter(instance.getQName());
362      writeGroupedModelObject(
363          instance,
364          item,
365          ((ObjectWriter<IBoundInstanceModelGroupedAssembly>) this::writeFlags)
366              .andThen(itemWriter::writeAssemblyModel));
367    }
368
369    @Override
370    public void writeItemAssembly(IBoundObject item, IBoundDefinitionModelAssembly definition) throws IOException {
371      // this is a special case where we are writing a top-level, potentially root,
372      // element. Need to take the object qname passed in
373      writeDefinitionObject(
374          definition,
375          item,
376          ((ObjectWriter<IBoundDefinitionModelAssembly>) this::writeFlags)
377              .andThen(this::writeAssemblyModel));
378    }
379
380    @Override
381    public void writeChoiceGroupItem(IBoundObject item, IBoundInstanceModelChoiceGroup instance) throws IOException {
382      IBoundInstanceModelGroupedNamed actualInstance = instance.getItemInstance(item);
383      assert actualInstance != null;
384      actualInstance.writeItem(item, this);
385    }
386  }
387
388  private abstract static class AbstractItemWriter implements IItemWriteHandler {
389    @NonNull
390    private final IEnhancedQName objectQName;
391
392    protected AbstractItemWriter(@NonNull IEnhancedQName qname) {
393      this.objectQName = qname;
394    }
395
396    /**
397     * Get the qualified name of the item's parent.
398     *
399     * @return the qualified name
400     */
401    @NonNull
402    protected IEnhancedQName getObjectQName() {
403      return objectQName;
404    }
405  }
406}