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.configuration.IMutableConfiguration;
11 import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
12 import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItemFactory;
13 import gov.nist.secauto.metaschema.core.model.IBoundObject;
14 import gov.nist.secauto.metaschema.core.util.AutoCloser;
15 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
16 import gov.nist.secauto.metaschema.databind.io.AbstractDeserializer;
17 import gov.nist.secauto.metaschema.databind.io.DeserializationFeature;
18 import gov.nist.secauto.metaschema.databind.model.IBoundDefinitionModelAssembly;
19
20 import org.codehaus.stax2.XMLEventReader2;
21 import org.codehaus.stax2.XMLInputFactory2;
22
23 import java.io.IOException;
24 import java.io.Reader;
25 import java.net.URI;
26
27 import javax.xml.stream.EventFilter;
28 import javax.xml.stream.XMLEventReader;
29 import javax.xml.stream.XMLInputFactory;
30 import javax.xml.stream.XMLResolver;
31 import javax.xml.stream.XMLStreamException;
32
33 import edu.umd.cs.findbugs.annotations.NonNull;
34 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
35 import nl.talsmasoftware.lazy4j.Lazy;
36
37 public class DefaultXmlDeserializer<CLASS extends IBoundObject>
38 extends AbstractDeserializer<CLASS> {
39 private Lazy<XMLInputFactory2> factory;
40
41 @NonNull
42 private final IBoundDefinitionModelAssembly rootDefinition;
43
44
45
46
47
48
49
50
51
52 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Use of final fields")
53 public DefaultXmlDeserializer(@NonNull IBoundDefinitionModelAssembly definition) {
54 super(definition);
55 this.rootDefinition = definition;
56 if (!definition.isRoot()) {
57 throw new UnsupportedOperationException(
58 String.format("The assembly '%s' is not a root assembly.", definition.getBoundClass().getName()));
59 }
60 resetFactory();
61 }
62
63 protected final void resetFactory() {
64 this.factory = Lazy.lazy(this::newFactoryInstance);
65 }
66
67 @Override
68 protected void configurationChanged(IMutableConfiguration<DeserializationFeature<?>> config) {
69 super.configurationChanged(config);
70 resetFactory();
71 }
72
73
74
75
76
77
78
79
80
81 @SuppressWarnings("resource")
82 @NonNull
83 protected XMLInputFactory2 newFactoryInstance() {
84 XMLInputFactory2 retval = (XMLInputFactory2) XMLInputFactory.newInstance();
85 assert retval instanceof WstxInputFactory;
86 retval.configureForXmlConformance();
87 retval.setProperty(XMLInputFactory.IS_COALESCING, false);
88 retval.setProperty(XMLInputFactory2.P_PRESERVE_LOCATION, true);
89
90
91 if (isFeatureEnabled(DeserializationFeature.DESERIALIZE_XML_ALLOW_ENTITY_RESOLUTION)) {
92 retval.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, true);
93 retval.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, true);
94 retval.setProperty(XMLInputFactory.SUPPORT_DTD, true);
95 retval.setProperty(XMLInputFactory.RESOLVER,
96 (XMLResolver) (publicID, systemID, baseURI, namespace) -> {
97 URI base = URI.create(baseURI);
98 URI resource = base.resolve(systemID);
99 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
112
113
114
115
116
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
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 }