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