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 037public class DefaultXmlDeserializer<CLASS extends IBoundObject> 038 extends AbstractDeserializer<CLASS> { 039 private Lazy<XMLInputFactory2> factory; 040 041 @NonNull 042 private final IBoundDefinitionModelAssembly rootDefinition; 043 044 /** 045 * Construct a new Module binding-based deserializer that reads XML-based Module 046 * content. 047 * 048 * @param definition 049 * the assembly class binding describing the Java objects this 050 * deserializer parses data into 051 */ 052 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields") 053 public DefaultXmlDeserializer(@NonNull IBoundDefinitionModelAssembly definition) { 054 super(definition); 055 this.rootDefinition = definition; 056 if (!definition.isRoot()) { 057 throw new UnsupportedOperationException( 058 String.format("The assembly '%s' is not a root assembly.", definition.getBoundClass().getName())); 059 } 060 resetFactory(); 061 } 062 063 protected final void resetFactory() { 064 this.factory = Lazy.lazy(this::newFactoryInstance); 065 } 066 067 @Override 068 protected void configurationChanged(IMutableConfiguration<DeserializationFeature<?>> config) { 069 super.configurationChanged(config); 070 resetFactory(); 071 } 072 073 /** 074 * Get a JSON factory instance. 075 * <p> 076 * This method can be used by sub-classes to create a customized factory 077 * instance. 078 * 079 * @return the factory 080 */ 081 @SuppressWarnings("resource") 082 @NonNull 083 protected XMLInputFactory2 newFactoryInstance() { 084 XMLInputFactory2 retval = (XMLInputFactory2) XMLInputFactory.newInstance(); 085 assert retval instanceof WstxInputFactory; 086 retval.configureForXmlConformance(); 087 retval.setProperty(XMLInputFactory.IS_COALESCING, false); 088 retval.setProperty(XMLInputFactory2.P_PRESERVE_LOCATION, true); 089 // xmlInputFactory.configureForSpeed(); 090 091 if (isFeatureEnabled(DeserializationFeature.DESERIALIZE_XML_ALLOW_ENTITY_RESOLUTION)) { 092 retval.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true); 093 retval.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true); 094 retval.setProperty(XMLInputFactory.SUPPORT_DTD, true); 095 retval.setProperty(XMLInputFactory.RESOLVER, 096 (XMLResolver) (publicID, systemID, baseURI, namespace) -> { 097 URI base = URI.create(baseURI); 098 URI resource = base.resolve(systemID); 099 try { 100 101 return ObjectUtils.notNull(resource.toURL().openStream()); 102 } catch (IOException ex) { 103 throw new XMLStreamException(ex); 104 } 105 }); 106 } 107 return retval; 108 } 109 110 /** 111 * Get the XML input factory instance used to create XML parser instances. 112 * <p> 113 * Uses a built-in default if a user specified factory is not provided. 114 * 115 * @return the factory instance 116 * @see #setXMLInputFactory(XMLInputFactory2) 117 */ 118 @NonNull 119 private XMLInputFactory2 getXMLInputFactory() { 120 return ObjectUtils.notNull(factory.get()); 121 } 122 123 @NonNull 124 private XMLEventReader2 newXMLEventReader2( 125 @NonNull URI documentUri, 126 @NonNull Reader reader) throws XMLStreamException { 127 XMLEventReader2 eventReader 128 = (XMLEventReader2) getXMLInputFactory().createXMLEventReader(documentUri.toASCIIString(), reader); 129 EventFilter filter = new CommentFilter(); 130 return ObjectUtils.notNull((XMLEventReader2) getXMLInputFactory().createFilteredReader(eventReader, filter)); 131 } 132 133 @Override 134 protected final IDocumentNodeItem deserializeToNodeItemInternal(Reader reader, URI documentUri) throws IOException { 135 Object value = deserializeToValueInternal(reader, documentUri); 136 return INodeItemFactory.instance().newDocumentNodeItem(rootDefinition, documentUri, value); 137 } 138 139 @Override 140 public final CLASS deserializeToValueInternal(Reader reader, URI documentUri) throws IOException { 141 // doesn't auto close the underlying reader 142 try (AutoCloser<XMLEventReader2, XMLStreamException> closer = AutoCloser.autoClose( 143 newXMLEventReader2(documentUri, reader), XMLEventReader::close)) { 144 return parseXmlInternal(closer.getResource()); 145 } catch (XMLStreamException ex) { 146 throw new IOException("Unable to create a new XMLEventReader2 instance.", ex); 147 } 148 } 149 150 @NonNull 151 private CLASS parseXmlInternal(@NonNull XMLEventReader2 reader) 152 throws IOException { 153 154 MetaschemaXmlReader parser = new MetaschemaXmlReader(reader, new DefaultXmlProblemHandler()); 155 156 try { 157 return parser.read(rootDefinition); 158 } catch (IOException | AssertionError ex) { 159 throw new IOException( 160 String.format("An unexpected error occurred during parsing: %s", ex.getMessage()), 161 ex); 162 } 163 } 164}