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 com.ctc.wstx.api.WstxOutputProperties;
9   import com.ctc.wstx.stax.WstxOutputFactory;
10  
11  import gov.nist.secauto.metaschema.core.model.IBoundObject;
12  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
13  import gov.nist.secauto.metaschema.databind.io.AbstractSerializer;
14  import gov.nist.secauto.metaschema.databind.io.SerializationFeature;
15  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
16  
17  import org.codehaus.stax2.XMLOutputFactory2;
18  import org.codehaus.stax2.XMLStreamWriter2;
19  
20  import java.io.IOException;
21  import java.io.Writer;
22  
23  import javax.xml.stream.XMLOutputFactory;
24  import javax.xml.stream.XMLStreamException;
25  
26  import edu.umd.cs.findbugs.annotations.NonNull;
27  
28  public class DefaultXmlSerializer<CLASS extends IBoundObject>
29      extends AbstractSerializer<CLASS> {
30    private XMLOutputFactory2 xmlOutputFactory;
31  
32    /**
33     * Construct a new XML serializer based on the top-level assembly indicated by
34     * the provided {@code classBinding}.
35     *
36     * @param definition
37     *          the bound Module assembly definition that describes the data to
38     *          serialize
39     */
40    public DefaultXmlSerializer(@NonNull IBoundDefinitionModelAssembly definition) {
41      super(definition);
42    }
43  
44    /**
45     * Get the configured XML output factory used to create {@link XMLStreamWriter2}
46     * instances.
47     *
48     * @return the factory
49     */
50    @NonNull
51    protected final XMLOutputFactory2 getXMLOutputFactory() {
52      synchronized (this) {
53        if (xmlOutputFactory == null) {
54          xmlOutputFactory = (XMLOutputFactory2) XMLOutputFactory.newInstance();
55          assert xmlOutputFactory instanceof WstxOutputFactory;
56          xmlOutputFactory.configureForSpeed();
57          xmlOutputFactory.setProperty(WstxOutputProperties.P_USE_DOUBLE_QUOTES_IN_XML_DECL, true);
58          xmlOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
59        }
60        assert xmlOutputFactory != null;
61        return xmlOutputFactory;
62      }
63    }
64  
65    /**
66     * Override the default {@link XMLOutputFactory2} instance with a custom
67     * factory.
68     *
69     * @param xmlOutputFactory
70     *          the new factory
71     */
72    protected void setXMLOutputFactory(@NonNull XMLOutputFactory2 xmlOutputFactory) {
73      synchronized (this) {
74        this.xmlOutputFactory = xmlOutputFactory;
75      }
76    }
77  
78    /**
79     * Create a new stream writer using the provided writer.
80     *
81     * @param writer
82     *          the writer to use for output
83     * @return the stream writer created by the output factory
84     * @throws IOException
85     *           if an error occurred while creating the writer
86     */
87    @NonNull
88    protected final XMLStreamWriter2 newXMLStreamWriter(@NonNull Writer writer) throws IOException {
89      try {
90        return ObjectUtils.notNull((XMLStreamWriter2) getXMLOutputFactory().createXMLStreamWriter(writer));
91      } catch (XMLStreamException ex) {
92        throw new IOException(ex);
93      }
94    }
95  
96    @Override
97    public void serialize(IBoundObject data, Writer writer) throws IOException {
98      XMLStreamWriter2 streamWriter = newXMLStreamWriter(writer);
99      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 }