org.eel.kitchen.jsonschema.validator.JsonResolver.java Source code

Java tutorial

Introduction

Here is the source code for org.eel.kitchen.jsonschema.validator.JsonResolver.java

Source

/*
 * Copyright (c) 2012, Francis Galiegue <fgaliegue@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Lesser GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Lesser GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.eel.kitchen.jsonschema.validator;

import com.fasterxml.jackson.databind.JsonNode;
import org.eel.kitchen.jsonschema.main.JsonSchemaException;
import org.eel.kitchen.jsonschema.ref.JsonRef;
import org.eel.kitchen.jsonschema.ref.SchemaContainer;
import org.eel.kitchen.jsonschema.ref.SchemaNode;
import org.eel.kitchen.jsonschema.ref.SchemaRegistry;
import org.eel.kitchen.jsonschema.report.Domain;
import org.eel.kitchen.jsonschema.report.Message;

import java.net.URI;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Class responsible for JSON Reference resolution
 *
 * <p>One instance of such class is created for each instance of {@link
 * JsonValidatorCache}.</p>
 */
final class JsonResolver {
    private final SchemaRegistry registry;

    JsonResolver(final SchemaRegistry registry) {
        this.registry = registry;
    }

    /**
     * Resolve a schema node to the target schema node
     *
     * @see SchemaRegistry#get(URI)
     *
     * @param schemaNode the original schema node
     * @return the computed schema node
     * @throws JsonSchemaException ref resolution failure (dangling ref, ref
     * loop, etc)
     */
    SchemaNode resolve(final SchemaNode schemaNode) throws JsonSchemaException {
        /*
         * All failures at this level are fatal
         */
        final Message.Builder msg = Domain.REF_RESOLVING.newMessage().setKeyword("$ref").setFatal(true);
        /*
         * These two elements might change during ref resolving. Set them to
         * their initial values.
         */
        SchemaContainer container = schemaNode.getContainer();
        JsonNode node = schemaNode.getNode();

        /*
         * This set will store all ABSOLUTE JSON references we encounter
         * during ref resolution. If there is an attempt to store an already
         * existing reference, this means a ref loop.
         *
         * We want to preserve insertion order, therefore we have to use a
         * LinkedHashSet.
         */
        final Set<JsonRef> refs = new LinkedHashSet<JsonRef>();

        /*
         * All elements below are set during the ref resolution process.
         */
        JsonRef source, ref, target;
        JsonNode refNode;

        while (true) {
            /*
             * We break out immediately if either there is no $ref node,
             * or there exists one but it is not a text node (syntax validation
             * will catch that).
             */
            refNode = node.path("$ref");
            if (!refNode.isTextual())
                break;
            /*
             * Similarly, the constructor will fail at this point iif the text
             * value of the node is not an URI: break, we want this caught by
             * syntax validation.
             */
            try {
                ref = JsonRef.fromString(refNode.textValue());
            } catch (JsonSchemaException ignored) {
                break;
            }
            /*
             * Compute the target ref. Try and insert it into the set of refs
             * already seen: if it has been already seen, there is a ref loop.
             */
            source = container.getLocator();
            target = source.resolve(ref);
            if (!refs.add(target)) {
                msg.setMessage("ref loop detected").addInfo("path", refs);
                throw new JsonSchemaException(msg.build());
            }
            /*
             * Should we change schema context? We should if the source ref (the
             * one in the current container) does not contain the target ref. In
             * this case, get the new container from our schema registry. If we
             * cannot do that, this is an error condition, bail out.
             */
            if (!source.contains(target))
                container = registry.get(target.getLocator());
            /*
             * Finally, compute the next node in the process. If it is missing,
             * we have a dangling JSON Pointer: this is an error condition.
             */
            node = target.getFragment().resolve(container.getSchema());
            if (node.isMissingNode()) {
                msg.setMessage("dangling JSON Reference").addInfo("ref", target);
                throw new JsonSchemaException(msg.build());
            }
        }

        return new SchemaNode(container, node);
    }
}