001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.core.model.validation; 028 029import gov.nist.secauto.metaschema.core.model.constraint.IConstraint.Level; 030import gov.nist.secauto.metaschema.core.util.ObjectUtils; 031 032import org.xml.sax.ErrorHandler; 033import org.xml.sax.SAXException; 034import org.xml.sax.SAXParseException; 035 036import java.io.IOException; 037import java.io.InputStream; 038import java.net.URI; 039import java.util.Collections; 040import java.util.LinkedList; 041import java.util.List; 042 043import javax.xml.XMLConstants; 044import javax.xml.transform.Source; 045import javax.xml.transform.stream.StreamSource; 046import javax.xml.validation.Schema; 047import javax.xml.validation.SchemaFactory; 048import javax.xml.validation.Validator; 049 050import edu.umd.cs.findbugs.annotations.NonNull; 051 052public class XmlSchemaContentValidator 053 extends AbstractContentValidator { 054 private final Schema schema; 055 056 @SuppressWarnings("null") 057 @NonNull 058 private static Schema toSchema(@NonNull List<? extends Source> schemaSources) throws SAXException { 059 SchemaFactory schemafactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 060 // schemafactory.setResourceResolver(new ClasspathResourceResolver()); 061 Schema retval; 062 if (schemaSources.isEmpty()) { 063 retval = schemafactory.newSchema(); 064 } else { 065 retval = schemafactory.newSchema(schemaSources.toArray(new Source[0])); 066 } 067 068 // TODO verify source input streams are closed 069 return retval; 070 } 071 072 public XmlSchemaContentValidator(@NonNull List<? extends Source> schemaSources) throws SAXException { 073 this(toSchema(ObjectUtils.requireNonNull(schemaSources, "schemaSources"))); 074 } 075 076 protected XmlSchemaContentValidator(@NonNull Schema schema) { 077 this.schema = ObjectUtils.requireNonNull(schema, "schema"); 078 } 079 080 public Schema getSchema() { 081 return schema; 082 } 083 084 @Override 085 public IValidationResult validate(InputStream is, URI documentUri) throws IOException { 086 Source xmlSource = new StreamSource(is, documentUri.toASCIIString()); 087 088 Validator validator = schema.newValidator(); 089 XmlValidationErrorHandler errorHandler = new XmlValidationErrorHandler(documentUri); 090 validator.setErrorHandler(errorHandler); 091 try { 092 validator.validate(xmlSource); 093 } catch (SAXException ex) { 094 throw new IOException(String.format("Unexpected failure during validation of '%s'", documentUri), ex); 095 } 096 return errorHandler; 097 } 098 099 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}