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; 030import gov.nist.secauto.metaschema.core.util.ObjectUtils; 031 032import org.everit.json.schema.Schema; 033import org.everit.json.schema.ValidationException; 034import org.everit.json.schema.loader.SchemaLoader; 035import org.json.JSONException; 036import org.json.JSONObject; 037import org.json.JSONTokener; 038 039import java.io.IOException; 040import java.io.InputStream; 041import java.io.Reader; 042import java.net.URI; 043import java.util.Collections; 044import java.util.List; 045import java.util.Objects; 046import java.util.stream.Collectors; 047import java.util.stream.Stream; 048 049import edu.umd.cs.findbugs.annotations.NonNull; 050 051public class JsonSchemaContentValidator 052 extends AbstractContentValidator { 053 @NonNull 054 private final Schema schema; 055 056 public JsonSchemaContentValidator(@NonNull Reader reader) { 057 this(new JSONTokener(reader)); 058 } 059 060 public JsonSchemaContentValidator(@NonNull InputStream is) { 061 this(new JSONTokener(is)); 062 } 063 064 public JsonSchemaContentValidator(@NonNull JSONObject jsonSchema) { 065 this(ObjectUtils.notNull(SchemaLoader.load(jsonSchema))); 066 } 067 068 protected JsonSchemaContentValidator(@NonNull JSONTokener tokenizer) { 069 this(new JSONObject(tokenizer)); 070 } 071 072 protected JsonSchemaContentValidator(@NonNull Schema schema) { 073 this.schema = ObjectUtils.requireNonNull(schema, "schema"); 074 } 075 076 @Override 077 public IValidationResult validate(InputStream is, URI documentUri) throws IOException { 078 JSONObject json; 079 try { 080 json = new JSONObject(new JSONTokener(is)); 081 } catch (JSONException ex) { 082 throw new IOException(String.format("Unable to parse JSON from '%s'", documentUri), ex); 083 } 084 return validate(json, documentUri); 085 } 086 087 @SuppressWarnings("null") 088 @NonNull 089 public IValidationResult validate(@NonNull JSONObject json, @NonNull URI documentUri) { 090 IValidationResult retval; 091 try { 092 schema.validate(json); 093 retval = IValidationResult.PASSING_RESULT; 094 } catch (ValidationException ex) { 095 retval = new JsonValidationResult( 096 handleValidationException(ex, documentUri) 097 .collect(Collectors.toList())); 098 } 099 100 return retval; 101 } 102 103 @SuppressWarnings("null") 104 @NonNull 105 protected Stream<JsonValidationFinding> handleValidationException(@NonNull ValidationException ex, 106 @NonNull URI documentUri) { 107 JsonValidationFinding finding = new JsonValidationFinding(ex, documentUri); 108 Stream<JsonValidationFinding> childFindings = ex.getCausingExceptions().stream() 109 .flatMap(exception -> { 110 return handleValidationException(exception, documentUri); 111 }); 112 return Stream.concat(Stream.of(finding), childFindings); 113 } 114 115 public static class JsonValidationFinding implements IValidationFinding { 116 @NonNull 117 private final ValidationException exception; 118 @NonNull 119 private final URI documentUri; 120 121 public JsonValidationFinding(@NonNull ValidationException exception, @NonNull URI documentUri) { 122 this.exception = ObjectUtils.requireNonNull(exception, "exception"); 123 this.documentUri = ObjectUtils.requireNonNull(documentUri, "documentUri"); 124 } 125 126 @Override 127 public IConstraint.Level getSeverity() { 128 return IConstraint.Level.ERROR; 129 } 130 131 @Override 132 public URI getDocumentUri() { 133 return documentUri; 134 } 135 136 @SuppressWarnings("null") 137 @Override 138 public String getMessage() { 139 return getCause().getLocalizedMessage(); 140 } 141 142 @NonNull 143 @Override 144 public ValidationException getCause() { 145 return exception; 146 } 147 } 148 149 private static class JsonValidationResult implements IValidationResult { 150 @NonNull 151 private final List<JsonValidationFinding> findings; 152 153 @SuppressWarnings("null") 154 public JsonValidationResult(@NonNull List<JsonValidationFinding> findings) { 155 this.findings = Collections.unmodifiableList(Objects.requireNonNull(findings, "findings")); 156 } 157 158 @Override 159 public IConstraint.Level getHighestSeverity() { 160 return findings.isEmpty() ? IConstraint.Level.INFORMATIONAL : IConstraint.Level.ERROR; 161 } 162 163 @Override 164 public List<? extends IValidationFinding> getFindings() { 165 return findings; 166 } 167 168 } 169}