1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package gov.nist.secauto.metaschema.core.model.validation;
28
29 import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.Level;
30 import gov.nist.secauto.metaschema.core.util.ObjectUtils;
31
32 import org.xml.sax.ErrorHandler;
33 import org.xml.sax.SAXException;
34 import org.xml.sax.SAXParseException;
35
36 import java.io.IOException;
37 import java.io.InputStream;
38 import java.net.URI;
39 import java.util.Collections;
40 import java.util.LinkedList;
41 import java.util.List;
42
43 import javax.xml.XMLConstants;
44 import javax.xml.transform.Source;
45 import javax.xml.transform.stream.StreamSource;
46 import javax.xml.validation.Schema;
47 import javax.xml.validation.SchemaFactory;
48 import javax.xml.validation.Validator;
49
50 import edu.umd.cs.findbugs.annotations.NonNull;
51
52 public class XmlSchemaContentValidator
53 extends AbstractContentValidator {
54 private final Schema schema;
55
56 @SuppressWarnings("null")
57 @NonNull
58 private static Schema toSchema(@NonNull List<? extends Source> schemaSources) throws SAXException {
59 SchemaFactory schemafactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
60
61 Schema retval;
62 if (schemaSources.isEmpty()) {
63 retval = schemafactory.newSchema();
64 } else {
65 retval = schemafactory.newSchema(schemaSources.toArray(new Source[0]));
66 }
67
68
69 return retval;
70 }
71
72 public XmlSchemaContentValidator(@NonNull List<? extends Source> schemaSources) throws SAXException {
73 this(toSchema(ObjectUtils.requireNonNull(schemaSources, "schemaSources")));
74 }
75
76 protected XmlSchemaContentValidator(@NonNull Schema schema) {
77 this.schema = ObjectUtils.requireNonNull(schema, "schema");
78 }
79
80 public Schema getSchema() {
81 return schema;
82 }
83
84 @Override
85 public IValidationResult validate(InputStream is, URI documentUri) throws IOException {
86 Source xmlSource = new StreamSource(is, documentUri.toASCIIString());
87
88 Validator validator = schema.newValidator();
89 XmlValidationErrorHandler errorHandler = new XmlValidationErrorHandler(documentUri);
90 validator.setErrorHandler(errorHandler);
91 try {
92 validator.validate(xmlSource);
93 } catch (SAXException ex) {
94 throw new IOException(String.format("Unexpected failure during validation of '%s'", documentUri), ex);
95 }
96 return errorHandler;
97 }
98
99 public static class XmlValidationFinding implements IValidationFinding {
100 @NonNull
101 private final URI documentUri;
102 @NonNull
103 private final SAXParseException exception;
104 @NonNull
105 private final Level severity;
106
107 public XmlValidationFinding(@NonNull Level severity, @NonNull SAXParseException exception,
108 @NonNull URI documentUri) {
109 this.documentUri = ObjectUtils.requireNonNull(documentUri, "documentUri");
110 this.exception = ObjectUtils.requireNonNull(exception, "exception");
111 this.severity = ObjectUtils.requireNonNull(severity, "severity");
112 }
113
114 @Override
115 public Level getSeverity() {
116 return severity;
117 }
118
119 @SuppressWarnings("null")
120 @Override
121 public URI getDocumentUri() {
122 String systemId = getCause().getSystemId();
123 return systemId == null ? documentUri : URI.create(systemId);
124 }
125
126 @SuppressWarnings("null")
127 @Override
128 public String getMessage() {
129 return getCause().getLocalizedMessage();
130 }
131
132 @NonNull
133 @Override
134 public SAXParseException getCause() {
135 return exception;
136 }
137 }
138
139 private static class XmlValidationErrorHandler implements ErrorHandler, IValidationResult {
140 @NonNull
141 private final URI documentUri;
142 @NonNull
143 private final List<XmlValidationFinding> findings = new LinkedList<>();
144 @NonNull
145 private Level highestSeverity = Level.INFORMATIONAL;
146
147 public XmlValidationErrorHandler(@NonNull URI documentUri) {
148 this.documentUri = ObjectUtils.requireNonNull(documentUri, "documentUri");
149 }
150
151 @NonNull
152 public URI getDocumentUri() {
153 return documentUri;
154 }
155
156 private void adjustHighestSeverity(@NonNull Level severity) {
157 if (highestSeverity.ordinal() < severity.ordinal()) {
158 highestSeverity = severity;
159 }
160 }
161
162 @SuppressWarnings("null")
163 @Override
164 public void warning(SAXParseException ex) throws SAXException {
165 findings.add(new XmlValidationFinding(Level.WARNING, ex, getDocumentUri()));
166 adjustHighestSeverity(Level.WARNING);
167 }
168
169 @SuppressWarnings("null")
170 @Override
171 public void error(SAXParseException ex) throws SAXException {
172 findings.add(new XmlValidationFinding(Level.ERROR, ex, getDocumentUri()));
173 adjustHighestSeverity(Level.ERROR);
174 }
175
176 @SuppressWarnings("null")
177 @Override
178 public void fatalError(SAXParseException ex) throws SAXException {
179 findings.add(new XmlValidationFinding(Level.CRITICAL, ex, getDocumentUri()));
180 adjustHighestSeverity(Level.CRITICAL);
181 }
182
183 @SuppressWarnings("null")
184 @Override
185 @NonNull
186 public List<XmlValidationFinding> getFindings() {
187 return Collections.unmodifiableList(findings);
188 }
189
190 @Override
191 public Level getHighestSeverity() {
192 return highestSeverity;
193 }
194 }
195 }