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.util.ObjectUtils;
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.namespace.QName;
037import javax.xml.stream.XMLStreamException;
038
039import edu.umd.cs.findbugs.annotations.NonNull;
040
041public class MetaschemaXmlWriter implements IXmlWritingContext {
042  @NonNull
043  private final XMLStreamWriter2 writer;
044
045  /**
046   * Construct a new Module-aware JSON writer.
047   *
048   * @param writer
049   *          the XML stream writer to write with
050   * @see DefaultJsonProblemHandler
051   */
052  public MetaschemaXmlWriter(
053      @NonNull XMLStreamWriter2 writer) {
054    this.writer = writer;
055  }
056
057  @Override
058  public XMLStreamWriter2 getWriter() {
059    return writer;
060  }
061
062  // =====================================
063  // Entry point for top-level-definitions
064  // =====================================
065
066  @Override
067  public void write(
068      @NonNull IBoundDefinitionModelComplex definition,
069      @NonNull IBoundObject item) throws IOException {
070
071    QName qname = definition.getXmlQName();
072
073    definition.writeItem(item, new ItemWriter(qname));
074  }
075
076  @Override
077  public void writeRoot(
078      @NonNull IBoundDefinitionModelAssembly definition,
079      @NonNull IBoundObject item) throws IOException {
080    definition.writeItem(item, new ItemWriter(ObjectUtils.requireNonNull(definition.getRootXmlQName())));
081  }
082
083  // ================
084  // Instance writers
085  // ================
086
087  private <T> void writeModelInstance(
088      @NonNull IBoundInstanceModel<T> instance,
089      @NonNull Object parentItem,
090      @NonNull ItemWriter itemWriter) throws IOException {
091    Object value = instance.getValue(parentItem);
092    if (value == null) {
093      return;
094    }
095
096    // this if is not strictly needed, since isEmpty will return false on a null
097    // value
098    // checking null here potentially avoids the expensive operation of
099    // 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}