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