1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.io.xml;
7   
8   import com.ctc.wstx.stax.WstxInputFactory;
9   
10  import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
11  import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItemFactory;
12  import gov.nist.secauto.metaschema.core.model.IBoundObject;
13  import gov.nist.secauto.metaschema.core.util.AutoCloser;
14  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
15  import gov.nist.secauto.metaschema.databind.io.AbstractDeserializer;
16  import gov.nist.secauto.metaschema.databind.io.DeserializationFeature;
17  import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
18  
19  import org.codehaus.stax2.XMLEventReader2;
20  import org.codehaus.stax2.XMLInputFactory2;
21  
22  import java.io.IOException;
23  import java.io.Reader;
24  import java.net.URI;
25  
26  import javax.xml.stream.EventFilter;
27  import javax.xml.stream.XMLEventReader;
28  import javax.xml.stream.XMLInputFactory;
29  import javax.xml.stream.XMLResolver;
30  import javax.xml.stream.XMLStreamException;
31  
32  import edu.umd.cs.findbugs.annotations.NonNull;
33  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
34  
35  public class DefaultXmlDeserializer<CLASS extends IBoundObject>
36      extends AbstractDeserializer<CLASS> {
37    private XMLInputFactory2 xmlInputFactory;
38  
39    @NonNull
40    private final IBoundDefinitionModelAssembly rootDefinition;
41  
42    /**
43     * Construct a new Module binding-based deserializer that reads XML-based Module
44     * content.
45     *
46     * @param definition
47     *          the assembly class binding describing the Java objects this
48     *          deserializer parses data into
49     */
50    @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
51    public DefaultXmlDeserializer(@NonNull IBoundDefinitionModelAssembly definition) {
52      super(definition);
53      this.rootDefinition = definition;
54      if (!definition.isRoot()) {
55        throw new UnsupportedOperationException(
56            String.format("The assembly '%s' is not a root assembly.", definition.getBoundClass().getName()));
57      }
58    }
59  
60    /**
61     * Get the XML input factory instance used to create XML parser instances.
62     * <p>
63     * Uses a built-in default if a user specified factory is not provided.
64     *
65     * @return the factory instance
66     * @see #setXMLInputFactory(XMLInputFactory2)
67     */
68    @NonNull
69    private XMLInputFactory2 getXMLInputFactory() {
70  
71      synchronized (this) {
72        if (xmlInputFactory == null) {
73          xmlInputFactory = (XMLInputFactory2) XMLInputFactory.newInstance();
74          assert xmlInputFactory instanceof WstxInputFactory;
75          xmlInputFactory.configureForXmlConformance();
76          xmlInputFactory.setProperty(XMLInputFactory.IS_COALESCING, false);
77          xmlInputFactory.setProperty(XMLInputFactory2.P_PRESERVE_LOCATION, true);
78          // xmlInputFactory.configureForSpeed();
79  
80          if (isFeatureEnabled(DeserializationFeature.DESERIALIZE_XML_ALLOW_ENTITY_RESOLUTION)) {
81            xmlInputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
82            xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
83            xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, true);
84            xmlInputFactory.setProperty(XMLInputFactory.RESOLVER,
85                (XMLResolver) (publicID, systemID, baseURI, namespace) -> {
86                  URI base = URI.create(baseURI);
87                  URI resource = base.resolve(systemID);
88                  try {
89                    return resource.toURL().openStream();
90                  } catch (IOException ex) {
91                    throw new XMLStreamException(ex);
92                  }
93                });
94          }
95        }
96        return ObjectUtils.notNull(xmlInputFactory);
97      }
98    }
99  
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 }