1
2
3
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
44
45
46
47
48
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
62
63
64
65
66
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
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
102
103
104
105
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
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 }