All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.openapiprocessor.jsonschema.schema.SchemaStore Maven / Gradle / Ivy

/*
 * Copyright 2021 https://github.com/openapi-processor/openapi-parser
 * PDX-License-Identifier: Apache-2.0
 */

package io.openapiprocessor.jsonschema.schema;

import io.openapiprocessor.jsonschema.support.Types;
import io.openapiprocessor.jsonschema.support.Uris;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.util.*;

import static io.openapiprocessor.jsonschema.support.Types.*;
import static io.openapiprocessor.jsonschema.support.Uris.createUri;

/**
 * Schema factory. This is used to register the schemas required to validate a json instant.
 */
public class SchemaStore {
    private static final Logger log = LoggerFactory.getLogger (SchemaStore.class);

    private final Map schemaCache = new HashMap<> ();

    private final DocumentStore documents;
    private final DocumentLoader loader;

    public SchemaStore (DocumentLoader loader) {
        this.documents = new DocumentStore ();
        this.loader = loader;
    }

    /**
     * download and register a schema document. {@code schemaUri} should be a json schema document downloadable from
     * the given uri.
     *
     * @param schemaUri schema uri/id
     */
    public void register (URI schemaUri) {
        // check absolute?

        if (documents.contains (schemaUri)) {
            warnDuplicateId(schemaUri);
            return;
        }

        Object document = loader.loadDocument (schemaUri);
        documents.addId (schemaUri, document);
    }

    /**
     * register a schema document. {@code schemaUri} is schema id of the given {@code document}.
     * The {@code document} must be a {@code Boolean} or a {@code Map}.
     *
     * @param schemaUri schema uri/id
     * @param document the document,
     */
    public void register (URI schemaUri, Object document) {
        // check absolute?

        if (documents.contains (schemaUri)) {
            warnDuplicateId(schemaUri);
            return;
        }

        documents.addId (schemaUri, document);
    }

    /**
     * register a schema document. Similar to {@code register()} with {@code schemaUri}, except
     * that the {@code schemaUri} gets generated. The {@code document} must be a {@code Boolean} or
     * a {@code Map}.
     *
     * @param document the document
     * @return the generated schema uri
     */
    public URI register (Object document) {
        URI schemaUri = generateUri ();
        documents.addId (schemaUri, document);
        return schemaUri;
    }

    /**
     * register a schema document. {@code resourcePath} should be a json schema document available
     * on the classpath (resource).
     *
     * @param schemaUri schema uri/id
     * @param resourcePath resource path
     */
    public void register (URI schemaUri, String resourcePath) {
        if (documents.contains (schemaUri)) {
            warnDuplicateId(schemaUri);
            return;
        }

        Object document = loader.loadDocument (resourcePath);
        documents.addId (schemaUri, document);
    }

    public void register (SchemaVersion version) {
        register (version.getSchemaResource ());
        version.getVocabularyResources ().forEach (this::register);
    }

    /**
     * register draft-202012 json schema.
     */
    public void registerDraft202012 () {
        register (SchemaVersion.Draft202012.getSchemaResource ());
        SchemaVersion.Draft202012.getVocabularyResources ().forEach (this::register);
    }

    /**
     * register draft-201909 json schema.
     */
    public void registerDraft201909 () {
        register (SchemaVersion.Draft201909.getSchemaResource ());
        SchemaVersion.Draft201909.getVocabularyResources ().forEach (this::register);
    }

    /**
     * register draft-7 json schema.
     */
    public void registerDraft7 () {
        register (SchemaVersion.Draft7.getSchemaResource ());
    }

    /**
     * register draft-6 json schema.
     */
    public void registerDraft6 () {
        register (SchemaVersion.Draft6.getSchemaResource ());
    }

    /**
     * register draft-4 json schema.
     */
    public void registerDraft4 () {
        register (SchemaVersion.Draft4.getSchemaResource ());
    }

    private void register (SchemaResource schema) {
        register (schema.getUri (), schema.getResource ());
    }

    /**
     * get a registered json schema. If the schema has no given meta schema it assumes the latest
     * (implemented) json schema draft.
     *
     * @param schemaUri schema id
     * @return the json schema
     */
    public JsonSchema getSchema (URI schemaUri) {
        return getSchema (schemaUri, SchemaVersion.getLatest ());
    }

    /**
     * get a registered json schema. If the schema has no given meta schema it is using the given
     * json schema draft {@code version} as meta schema.
     *
     * @param schemaUri schema id
     * @param version fallback json schema version.
     * @return the json schema
     */
    public JsonSchema getSchema (URI schemaUri, SchemaVersion version) {
        JsonSchema schema = schemaCache.get (schemaUri);
        if (schema != null) {
            return schema;
        }

        Object document = documents.get (schemaUri);
        if (document == null) {
            // todo NotRegisteredException
            throw new RuntimeException ();
        }

        // create schema
        Resolver resolver = new Resolver (documents, loader);
        ResolverResult resolve = resolver.resolve (schemaUri, document, new Resolver.Settings (version));
        schema = createSchema (schemaUri, version, resolve);

        schemaCache.put (schemaUri, schema);
        return schema;
    }

    private JsonSchema createSchema (URI schemaUri, SchemaVersion version, ResolverResult result) {
        Scope scope = result.getScope ();
        Object document = result.getDocument ();

        if (Types.isBoolean (document)) {
            Vocabularies vocabularies = Vocabularies.ALL;

            return new JsonSchemaBoolean (
                Types.asBoolean (document),
                new JsonSchemaContext (scope, new ReferenceRegistry (), vocabularies));

        } else if (Types.isObject (document)) {
            Map object = Types.asObject (document);
            Vocabularies vocabularies = getVocabularies (schemaUri, version, object);

            return new JsonSchemaObject (object, new JsonSchemaContext (scope, result.getRegistry (), vocabularies));
        } else {
            // todo
            throw new RuntimeException ();
        }
    }

    private Vocabularies getVocabularies (URI schemaUri, SchemaVersion version, Map document) {
        URI metaSchemaUri = getMetaSchemaUri(document);
        if (metaSchemaUri == null) {
            return Vocabularies.ALL;
        }

        SchemaVersion metaVersion = getMetaSchemaVersion(metaSchemaUri, version);
        Map metaObject = getDocument(metaSchemaUri);
        if (metaObject == null) {
            return Vocabularies.ALL;
        }

        return getVocabularies(metaObject, metaVersion);
    }

    private @Nullable SchemaVersion getMetaSchemaVersion(URI schemaUri, SchemaVersion version) {
        SchemaVersion schemaVersion = SchemaVersion.getVersion(schemaUri);
        if (schemaVersion != null)
            return schemaVersion;

        Map document = getDocument(schemaUri);
        if (document == null)
            return version;

        URI metaSchemaUri = getMetaSchemaUri(document);
        if (metaSchemaUri == null) {
            return version;
        }

        SchemaVersion metaVersion = SchemaVersion.getVersion (metaSchemaUri);
        if (metaVersion != null)
            return metaVersion;

        return version;
    }

    private @Nullable URI getMetaSchemaUri(Map schema) {
        Object schemaValue = schema.get (Keywords.SCHEMA);
        if (!Types.isString (schemaValue))
            return null;

        return Uris.createUri (Types.asString(schemaValue));
    }

    private @Nullable Map getDocument(URI schemaUri) {
        Object document = documents.get (schemaUri);
        if (document == null) {
            // todo throw unknown meta schema
            throw new RuntimeException ();
        }

        if (!isObject(document))
            return null;

        return asObject(document);
    }

    private Vocabularies getVocabularies (Map document, SchemaVersion version) {
        Object vocabularyValue = document.get(Keywords.VOCABULARY);
        if (!isObject(vocabularyValue))
            return Vocabularies.ALL;

        Map vocabularyObject = asObject(vocabularyValue);

        Map vocabularies = new LinkedHashMap<> ();
        vocabularyObject.forEach ((propKey, propValue) -> {
            vocabularies.put (createUri (propKey), asBoolean (propValue));
        });

        return Vocabularies.create (vocabularies, version);
    }

    private URI generateUri () {
        return URI.create (String.format ("https://%s/", UUID.randomUUID ()));
    }

    public DocumentStore getDocuments () {
        return documents;
    }

    private static void warnDuplicateId(URI schemaUri) {
        log.warn ("id is already registered: {}", schemaUri.toString ());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy