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