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}