001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.databind.io.xml;
007
008import com.ctc.wstx.api.WstxOutputProperties;
009import com.ctc.wstx.stax.WstxOutputFactory;
010
011import gov.nist.secauto.metaschema.core.model.IBoundObject;
012import gov.nist.secauto.metaschema.core.util.ObjectUtils;
013import gov.nist.secauto.metaschema.databind.io.AbstractSerializer;
014import gov.nist.secauto.metaschema.databind.io.SerializationFeature;
015import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
016
017import org.codehaus.stax2.XMLOutputFactory2;
018import org.codehaus.stax2.XMLStreamWriter2;
019
020import java.io.IOException;
021import java.io.Writer;
022
023import javax.xml.stream.XMLOutputFactory;
024import javax.xml.stream.XMLStreamException;
025
026import edu.umd.cs.findbugs.annotations.NonNull;
027
028public class DefaultXmlSerializer<CLASS extends IBoundObject>
029    extends AbstractSerializer<CLASS> {
030  private XMLOutputFactory2 xmlOutputFactory;
031
032  /**
033   * Construct a new XML serializer based on the top-level assembly indicated by
034   * the provided {@code classBinding}.
035   *
036   * @param definition
037   *          the bound Module assembly definition that describes the data to
038   *          serialize
039   */
040  public DefaultXmlSerializer(@NonNull IBoundDefinitionModelAssembly definition) {
041    super(definition);
042  }
043
044  /**
045   * Get the configured XML output factory used to create {@link XMLStreamWriter2}
046   * instances.
047   *
048   * @return the factory
049   */
050  @NonNull
051  protected final XMLOutputFactory2 getXMLOutputFactory() {
052    synchronized (this) {
053      if (xmlOutputFactory == null) {
054        xmlOutputFactory = (XMLOutputFactory2) XMLOutputFactory.newInstance();
055        assert xmlOutputFactory instanceof WstxOutputFactory;
056        xmlOutputFactory.configureForSpeed();
057        xmlOutputFactory.setProperty(WstxOutputProperties.P_USE_DOUBLE_QUOTES_IN_XML_DECL, true);
058        xmlOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
059      }
060      assert xmlOutputFactory != null;
061      return xmlOutputFactory;
062    }
063  }
064
065  /**
066   * Override the default {@link XMLOutputFactory2} instance with a custom
067   * factory.
068   *
069   * @param xmlOutputFactory
070   *          the new factory
071   */
072  protected void setXMLOutputFactory(@NonNull XMLOutputFactory2 xmlOutputFactory) {
073    synchronized (this) {
074      this.xmlOutputFactory = xmlOutputFactory;
075    }
076  }
077
078  /**
079   * Create a new stream writer using the provided writer.
080   *
081   * @param writer
082   *          the writer to use for output
083   * @return the stream writer created by the output factory
084   * @throws IOException
085   *           if an error occurred while creating the writer
086   */
087  @NonNull
088  protected final XMLStreamWriter2 newXMLStreamWriter(@NonNull Writer writer) throws IOException {
089    try {
090      return ObjectUtils.notNull((XMLStreamWriter2) getXMLOutputFactory().createXMLStreamWriter(writer));
091    } catch (XMLStreamException ex) {
092      throw new IOException(ex);
093    }
094  }
095
096  @Override
097  public void serialize(IBoundObject data, Writer writer) throws IOException {
098    XMLStreamWriter2 streamWriter = newXMLStreamWriter(writer);
099    IOException caughtException = null;
100    IBoundDefinitionModelAssembly definition = getDefinition();
101
102    MetaschemaXmlWriter xmlGenerator = new MetaschemaXmlWriter(streamWriter);
103
104    boolean serializeRoot = get(SerializationFeature.SERIALIZE_ROOT);
105    try {
106      if (serializeRoot) {
107        streamWriter.writeStartDocument("UTF-8", "1.0");
108        xmlGenerator.writeRoot(definition, data);
109      } else {
110        xmlGenerator.write(definition, data);
111      }
112
113      streamWriter.flush();
114
115      if (serializeRoot) {
116        streamWriter.writeEndDocument();
117      }
118    } catch (XMLStreamException ex) {
119      caughtException = new IOException(ex);
120      throw caughtException;
121    } finally { // NOPMD - exception handling is needed
122      try {
123        streamWriter.close();
124      } catch (XMLStreamException ex) {
125        if (caughtException == null) {
126          throw new IOException(ex);
127        }
128        caughtException.addSuppressed(ex);
129        throw caughtException; // NOPMD - intentional
130      }
131    }
132  }
133}