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.stax.WstxInputFactory; 009 010import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration; 011import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem; 012import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItemFactory; 013import gov.nist.secauto.metaschema.core.model.IBoundObject; 014import gov.nist.secauto.metaschema.core.util.AutoCloser; 015import gov.nist.secauto.metaschema.core.util.ObjectUtils; 016import gov.nist.secauto.metaschema.databind.io.AbstractDeserializer; 017import gov.nist.secauto.metaschema.databind.io.DeserializationFeature; 018import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly; 019 020import org.codehaus.stax2.XMLEventReader2; 021import org.codehaus.stax2.XMLInputFactory2; 022 023import java.io.IOException; 024import java.io.Reader; 025import java.net.URI; 026 027import javax.xml.stream.EventFilter; 028import javax.xml.stream.XMLEventReader; 029import javax.xml.stream.XMLInputFactory; 030import javax.xml.stream.XMLResolver; 031import javax.xml.stream.XMLStreamException; 032 033import edu.umd.cs.findbugs.annotations.NonNull; 034import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 035import nl.talsmasoftware.lazy4j.Lazy; 036 037/** 038 * Provides support for reading XML-based data based on a bound Metaschema 039 * module. 040 * 041 * @param <CLASS> 042 * the Java type of the bound object representing the root node to read 043 */ 044public class DefaultXmlDeserializer<CLASS extends IBoundObject> 045 extends AbstractDeserializer<CLASS> { 046 private Lazy<XMLInputFactory2> factory; 047 048 @NonNull 049 private final IBoundDefinitionModelAssembly rootDefinition; 050 051 /** 052 * Construct a new Module binding-based deserializer that reads XML-based Module 053 * content. 054 * 055 * @param definition 056 * the assembly class binding describing the Java objects this 057 * deserializer parses data into 058 */ 059 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields") 060 public DefaultXmlDeserializer(@NonNull IBoundDefinitionModelAssembly definition) { 061 super(definition); 062 this.rootDefinition = definition; 063 if (!definition.isRoot()) { 064 throw new UnsupportedOperationException( 065 String.format("The assembly '%s' is not a root assembly.", definition.getBoundClass().getName())); 066 } 067 resetFactory(); 068 } 069 070 /** 071 * For use by subclasses to reset the underlying XML factory when an important 072 * change has occurred that will change how the factory produces an 073 * {@link XMLInputFactory2}. 074 */ 075 protected final void resetFactory() { 076 this.factory = Lazy.lazy(this::newFactoryInstance); 077 } 078 079 @Override 080 protected void configurationChanged(IMutableConfiguration<DeserializationFeature<?>> config) { 081 super.configurationChanged(config); 082 resetFactory(); 083 } 084 085 /** 086 * Get a JSON factory instance. 087 * <p> 088 * This method can be used by sub-classes to create a customized factory 089 * instance. 090 * 091 * @return the factory 092 */ 093 @SuppressWarnings("resource") 094 @NonNull 095 protected XMLInputFactory2 newFactoryInstance() { 096 XMLInputFactory2 retval = (XMLInputFactory2) XMLInputFactory.newInstance(); 097 assert retval instanceof WstxInputFactory; 098 retval.configureForXmlConformance(); 099 retval.setProperty(XMLInputFactory.IS_COALESCING, false); 100 retval.setProperty(XMLInputFactory2.P_PRESERVE_LOCATION, true); 101 // xmlInputFactory.configureForSpeed(); 102 103 if (isFeatureEnabled(DeserializationFeature.DESERIALIZE_XML_ALLOW_ENTITY_RESOLUTION)) { 104 retval.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true); 105 retval.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true); 106 retval.setProperty(XMLInputFactory.SUPPORT_DTD, true); 107 retval.setProperty(XMLInputFactory.RESOLVER, 108 (XMLResolver) (publicID, systemID, baseURI, namespace) -> { 109 URI base = URI.create(baseURI); 110 URI resource = base.resolve(systemID); 111 try { 112 113 return ObjectUtils.notNull(resource.toURL().openStream()); 114 } catch (IOException ex) { 115 throw new XMLStreamException(ex); 116 } 117 }); 118 } 119 return retval; 120 } 121 122 /** 123 * Get the XML input factory instance used to create XML parser instances. 124 * <p> 125 * Uses a built-in default if a user specified factory is not provided. 126 * 127 * @return the factory instance 128 * @see #setXMLInputFactory(XMLInputFactory2) 129 */ 130 @NonNull 131 private XMLInputFactory2 getXMLInputFactory() { 132 return ObjectUtils.notNull(factory.get()); 133 } 134 135 @NonNull 136 private XMLEventReader2 newXMLEventReader2( 137 @NonNull URI documentUri, 138 @NonNull Reader reader) throws XMLStreamException { 139 XMLEventReader2 eventReader 140 = (XMLEventReader2) getXMLInputFactory().createXMLEventReader(documentUri.toASCIIString(), reader); 141 EventFilter filter = new CommentFilter(); 142 return ObjectUtils.notNull((XMLEventReader2) getXMLInputFactory().createFilteredReader(eventReader, filter)); 143 } 144 145 @Override 146 protected final IDocumentNodeItem deserializeToNodeItemInternal(Reader reader, URI documentUri) throws IOException { 147 Object value = deserializeToValueInternal(reader, documentUri); 148 return INodeItemFactory.instance().newDocumentNodeItem(rootDefinition, documentUri, value); 149 } 150 151 @Override 152 public final CLASS deserializeToValueInternal(Reader reader, URI resource) throws IOException { 153 // doesn't auto close the underlying reader 154 try (AutoCloser<XMLEventReader2, XMLStreamException> closer = AutoCloser.autoClose( 155 newXMLEventReader2(resource, reader), XMLEventReader::close)) { 156 return parseXmlInternal(closer.getResource(), resource); 157 } catch (XMLStreamException ex) { 158 throw new IOException("Unable to create a new XMLEventReader2 instance.", ex); 159 } 160 } 161 162 @NonNull 163 private CLASS parseXmlInternal(@NonNull XMLEventReader2 reader, @NonNull URI resource) 164 throws IOException { 165 166 MetaschemaXmlReader parser = new MetaschemaXmlReader(reader, resource, new DefaultXmlProblemHandler()); 167 168 try { 169 return parser.read(rootDefinition); 170 } catch (IOException | AssertionError ex) { 171 throw new IOException( 172 String.format("An unexpected error occurred during parsing: %s", ex.getMessage()), 173 ex); 174 } 175 } 176}