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}