Java tutorial
/* * Copyright (C) 2011 Everit Kft. (http://www.everit.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.everit.json.schema; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import org.json.JSONArray; import org.json.JSONObject; /** * Thrown by {@link Schema} subclasses on validation failure. */ public class ValidationException extends RuntimeException { private static final long serialVersionUID = 6192047123024651924L; private static int getViolationCount(final List<ValidationException> causes) { int causeCount = causes.stream().mapToInt(ValidationException::getViolationCount).sum(); return Math.max(1, causeCount); } /** * Sort of static factory method. It is used by {@link ObjectSchema} and {@link ArraySchema} to * create {@code ValidationException}s, handling the case of multiple violations occuring during * validation. * * <ul> * <li>If {@code failures} is empty, then it doesn't do anything</li> * <li>If {@code failures} contains 1 exception instance, then that will be thrown</li> * <li>Otherwise a new exception instance will be created, its {@link #getViolatedSchema() * violated schema} will be {@code rootFailingSchema}, and its {@link #getCausingExceptions() * causing exceptions} will be the {@code failures} list</li> * </ul> * * @param rootFailingSchema * the schema which detected the {@code failures} * @param failures * list containing validation failures to be thrown by this method */ public static void throwFor(final Schema rootFailingSchema, final List<ValidationException> failures) { int failureCount = failures.size(); if (failureCount == 0) { return; } else if (failureCount == 1) { throw failures.get(0); } else { throw new ValidationException(rootFailingSchema, new ArrayList<>(failures)); } } private final StringBuilder pointerToViolation; private final transient Schema violatedSchema; private final List<ValidationException> causingExceptions; private final String keyword; /** * Deprecated, use {@code ValidationException(Schema, Class<?>, Object)} instead. * * @param expectedType * the expected type * @param actualValue * the violating value */ @Deprecated public ValidationException(final Class<?> expectedType, final Object actualValue) { this(null, expectedType, actualValue); } /** * Constructor, creates an instance with {@code keyword="type"}. * * @param violatedSchema * the schema instance which detected the schema violation * @param expectedType * the expected type * @param actualValue * the violating value */ public ValidationException(final Schema violatedSchema, final Class<?> expectedType, final Object actualValue) { this(violatedSchema, expectedType, actualValue, "type"); } /** * Constructor for type-mismatch failures. It is usually more convenient to use * {@link #ValidationException(Schema, Class, Object)} instead. * * @param violatedSchema * the schema instance which detected the schema violation * @param expectedType * the expected type * @param actualValue * the violating value * @param keyword * the violating keyword */ public ValidationException(final Schema violatedSchema, final Class<?> expectedType, final Object actualValue, final String keyword) { this(violatedSchema, new StringBuilder("#"), "expected type: " + expectedType.getSimpleName() + ", found: " + (actualValue == null ? "null" : actualValue.getClass().getSimpleName()), Collections.emptyList(), keyword); } private ValidationException(final Schema rootFailingSchema, final List<ValidationException> causingExceptions) { this(rootFailingSchema, new StringBuilder("#"), getViolationCount(causingExceptions) + " schema violations found", causingExceptions); } /** * Constructor. * * @param violatedSchema * the schema instance which detected the schema violation * @param message * the readable exception message * @deprecated use one of the constructors which explicitly specify the violated keyword instead */ @Deprecated public ValidationException(final Schema violatedSchema, final String message) { this(violatedSchema, new StringBuilder("#"), message, Collections.emptyList()); } /** * Constructor. * * @param violatedSchema * the schama instance which detected the schema violation * @param message * the readable exception message * @param keyword * the violated keyword */ public ValidationException(final Schema violatedSchema, final String message, final String keyword) { this(violatedSchema, new StringBuilder("#"), message, Collections.emptyList(), keyword); } /*** * Constructor. * * @param violatedSchema * the schema instance which detected the schema violation * @param pointerToViolation * a JSON pointer denoting the part of the document which violates the schema * @param message * the readable exception message * @param causingExceptions * a (possibly empty) list of validation failures. It is used if multiple schema * violations are found by violatedSchema * @deprecated please explicitly specify the violated keyword using one of these constructors: * <ul> * <li>{@link #ValidationException(Schema, StringBuilder, String, List, String)} * <li>{@link #ValidationException(Schema, String, String)} * <li>{@link #ValidationException(Schema, Class, Object, String)} * </ul> */ @Deprecated ValidationException(final Schema violatedSchema, final StringBuilder pointerToViolation, final String message, final List<ValidationException> causingExceptions) { this(violatedSchema, pointerToViolation, message, causingExceptions, null); } /*** * Constructor. * * @param violatedSchema * the schema instance which detected the schema violation * @param pointerToViolation * a JSON pointer denoting the part of the document which violates the schema * @param message * the readable exception message * @param causingExceptions * a (possibly empty) list of validation failures. It is used if multiple schema * violations are found by violatedSchema * @param keyword * the violated keyword */ ValidationException(final Schema violatedSchema, final StringBuilder pointerToViolation, final String message, final List<ValidationException> causingExceptions, final String keyword) { super(message); this.violatedSchema = violatedSchema; this.pointerToViolation = pointerToViolation; this.causingExceptions = Collections.unmodifiableList(causingExceptions); this.keyword = keyword; } /** * Deprecated, use {@code ValidationException(Schema, String)} instead. * * @param message * readable exception message */ @Deprecated public ValidationException(final String message) { this((Schema) null, new StringBuilder("#"), message, Collections.emptyList()); } private ValidationException(final StringBuilder pointerToViolation, final Schema violatedSchema, final String message, final List<ValidationException> causingExceptions, final String keyword) { this(violatedSchema, pointerToViolation, message, causingExceptions, keyword); } /** * Constructor. * * @param violatedSchema * the schema instance which detected the schema violation * @param message * the readable exception message * @param causingExceptions * a (possibly empty) list of validation failures. It is used if multiple schema * violations are found by violatedSchema * @deprecated use one of the constructors which explicitly specify the keyword instead */ @Deprecated public ValidationException(final Schema violatedSchema, final String message, final List<ValidationException> causingExceptions) { this(violatedSchema, new StringBuilder("#"), message, causingExceptions); } private String escapeFragment(final String fragment) { return fragment.replace("~", "~0").replace("/", "~1"); } public List<ValidationException> getCausingExceptions() { return causingExceptions; } /** * Returns a programmer-readable error description prepended by {@link #getPointerToViolation() * the pointer to the violating fragment} of the JSON document. * * @return the error description */ @Override public String getMessage() { return getPointerToViolation() + ": " + super.getMessage(); } /** * Returns a programmer-readable error description. Unlike {@link #getMessage()} this doesn't * contain the JSON pointer denoting the violating document fragment. * * * @return the error description */ public String getErrorMessage() { return super.getMessage(); } /** * A JSON pointer denoting the part of the document which violates the schema. It always points * from the root of the document to the violating data fragment, therefore it always starts with * <code>#</code>. * * @return the JSON pointer */ public String getPointerToViolation() { if (pointerToViolation == null) { return null; } return pointerToViolation.toString(); } public Schema getViolatedSchema() { return violatedSchema; } /** * Creates a new {@code ViolationException} instance based on this one, but with changed * {@link #getPointerToViolation() JSON pointer}. * * @param fragment * the fragment of the JSON pointer to be prepended to existing pointers * @return the new instance */ public ValidationException prepend(final String fragment) { return prepend(fragment, this.violatedSchema); } /** * Creates a new {@code ViolationException} instance based on this one, but with changed * {@link #getPointerToViolation() JSON pointer} and {link {@link #getViolatedSchema() violated * schema}. * * @param fragment * the fragment of the JSON pointer to be prepended to existing pointers * @param violatedSchema * the violated schema, which may not be the same as {@link #getViolatedSchema()} * @return the new {@code ViolationException} instance */ public ValidationException prepend(final String fragment, final Schema violatedSchema) { String escapedFragment = escapeFragment(Objects.requireNonNull(fragment, "fragment cannot be null")); StringBuilder newPointer = this.pointerToViolation.insert(1, '/').insert(2, escapedFragment); List<ValidationException> prependedCausingExceptions = causingExceptions.stream() .map(exc -> exc.prepend(escapedFragment)).collect(Collectors.toList()); return new ValidationException(newPointer, violatedSchema, super.getMessage(), prependedCausingExceptions, this.keyword); } public int getViolationCount() { return getViolationCount(causingExceptions); } public String getKeyword() { return keyword; } /** * Creates a JSON representation of the failure. * * The returned {@code JSONObject} contains the following keys: * <ul> * <li>{@code "message"}: a programmer-friendly exception message. This value is a non-nullable * string.</li> * <li>{@code "keyword"}: a JSON Schema keyword which was used in the schema and violated by the * input JSON. This value is a nullable string.</li> * <li>{@code "pointerToViolation"}: a JSON Pointer denoting the path from the root of the * document to the invalid fragment of it. This value is a non-nullable string. See * {@link #getPointerToViolation()}</li> * <li>{@code "causingExceptions"}: is a (possibly empty) array of violations which caused this * exceptions. See {@link #getCausingExceptions()}</li> * </ul> * * @return a JSON description of the validation error */ public JSONObject toJSON() { JSONObject rval = new JSONObject(); rval.put("keyword", keyword); if (pointerToViolation == null) { rval.put("pointerToViolation", JSONObject.NULL); } else { rval.put("pointerToViolation", getPointerToViolation()); } rval.put("message", super.getMessage()); List<JSONObject> causeJsons = causingExceptions.stream().map(ValidationException::toJSON) .collect(Collectors.toList()); rval.put("causingExceptions", new JSONArray(causeJsons)); return rval; } }