Customizing Schema Retrieval

A schema can be identified by its schema identifier which is indicated using the $id keyword or id keyword in earlier drafts. This is an absolute IRI that uniquely identifies the schema and is not necessarily a network locator. A schema need not be downloadable from it's absolute IRI.

In the event a schema references a schema identifier that is not a subschema resource, for instance defined in the $defs keyword or definitions keyword. The library will need to be able to retrieve the schema given its schema identifier.

In the event that the schema does not define a schema identifier using the $id keyword, the retrieval IRI will be used as it's schema identifier.

Loading Schemas from memory

Schemas can be loaded through a map.

String schemaData = "{\r\n"
        + "  \"type\": \"integer\"\r\n"
        + "}";
Map<String, String> schemas = Collections.singletonMap("https://www.example.com/integer.json", schemaData); 
JsonSchemaFactory schemaFactory = JsonSchemaFactory
    .getInstance(VersionFlag.V7,
        builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(schemas)));

Schemas can be loaded through a function.

String schemaData = "{\r\n"
        + "  \"type\": \"integer\"\r\n"
        + "}";
Map<String, String> schemas = Collections.singletonMap("https://www.example.com/integer.json", schemaData); 
    JsonSchemaFactory schemaFactory = JsonSchemaFactory
        .getInstance(VersionFlag.V7,
            builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(schemas::get)));

Schemas can also be loaded in the following manner.

class RegistryEntry {
    private final String schemaData;

    public RegistryEntry(String schemaData) {
        this.schemaData = schemaData;
    }

    public String getSchemaData() {
        return this.schemaData;
    }
}

String schemaData = "{\r\n"
        + "  \"type\": \"integer\"\r\n"
        + "}";
Map<String, RegistryEntry> registry = Collections
    .singletonMap("https://www.example.com/integer.json", new RegistryEntry(schemaData));
JsonSchemaFactory schemaFactory = JsonSchemaFactory
    .getInstance(VersionFlag.V7, builder -> builder
        .schemaLoaders(schemaLoaders -> schemaLoaders.schemas(registry::get, RegistryEntry::getSchemaData)));

Mapping Schema Identifier to Retrieval IRI

The schema identifier can be mapped to the retrieval IRI by implementing the SchemaMapper interface.

Configuring Schema Mapper

class CustomSchemaMapper implements SchemaMapper {
    @Override
    public AbsoluteIri map(AbsoluteIri absoluteIRI) {
        String iri = absoluteIRI.toString();
        if ("https://www.example.com/integer.json".equals(iri)) {
            return AbsoluteIri.of("classpath:schemas/integer.json");
        }
        return null;
    }
}

JsonSchemaFactory schemaFactory = JsonSchemaFactory
    .getInstance(VersionFlag.V7,
        builder -> builder.schemaMappers(schemaMappers -> schemaMappers.add(new CustomSchemaMapper())));

Configuring Prefix Mappings

JsonSchemaFactory schemaFactory = JsonSchemaFactory
    .getInstance(VersionFlag.V7,
        builder -> builder
            .schemaMappers(schemaMappers -> schemaMappers
                .mapPrefix("https://json-schema.org", "classpath:")
                .mapPrefix("http://json-schema.org", "classpath:")));

Configuring Mappings

Map<String, String> mappings = Collections
    .singletonMap("https://www.example.com/integer.json", "classpath:schemas/integer.json");

JsonSchemaFactory schemaFactory = JsonSchemaFactory
    .getInstance(VersionFlag.V7,
        builder -> builder.schemaMappers(schemaMappers -> schemaMappers.mappings(mappings)));

Customizing Network Schema Retrieval

The default UriSchemaLoader implementation uses JDK connection/socket without handling network exceptions. It works in most of the cases; however, if you want to have a customized implementation, you can do so. One user has his implementation with urirest to handle the timeout. A detailed discussion can be found in this issue

Configuring Custom URI Schema Loader

The default UriSchemaLoader can be overwritten in order to customize its behaviour in regards of authorization or error handling.

The SchemaLoader interface must implemented and the implementation configured on the JsonSchemaFactory.

public class CustomUriSchemaLoader implements SchemaLoader {
    private static final Logger LOGGER = LoggerFactory.getLogger(CustomUriSchemaLoader.class);
    private final String        authorizationToken;
    private final HttpClient    client;

    public CustomUriSchemaLoader(String authorizationToken) {
        this.authorizationToken = authorizationToken;
        this.client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
    }

    @Override
    public InputStreamSource getSchema(AbsoluteIri absoluteIri) {
        String scheme = absoluteIri.getScheme();
        if ("https".equals(scheme) || "http".equals(scheme)) {
            URI uri = URI.create(absoluteIri.toString());
            return () -> {
                HttpRequest request = HttpRequest.newBuilder().uri(uri).header("Authorization", authorizationToken).build();
                try {
                    HttpResponse<String> response = this.client.send(request, HttpResponse.BodyHandlers.ofString());
                    if ((200 > response.statusCode()) || (response.statusCode() > 299)) {
                        String errorMessage = String.format("Could not get data from schema endpoint. The following status %d was returned.", response.statusCode());
                        LOGGER.error(errorMessage);
                    }
                    return new ByteArrayInputStream(response.body().getBytes(StandardCharsets.UTF_8));
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return null;
    }
}

Within the JsonSchemaFactory the custom SchemaLoader must be configured.

CustomUriSchemaLoader uriSchemaLoader = new CustomUriSchemaLoader(authorizationToken);

JsonSchemaFactory schemaFactory = JsonSchemaFactory
    .getInstance(VersionFlag.V7,
        builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.add(uriSchemaLoader)));