1# Customizing Schema Retrieval 2 3A 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. 4 5In 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. 6 7In 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. 8 9## Loading Schemas from memory 10 11Schemas can be loaded through a map. 12 13```java 14String schemaData = "{\r\n" 15 + " \"type\": \"integer\"\r\n" 16 + "}"; 17Map<String, String> schemas = Collections.singletonMap("https://www.example.com/integer.json", schemaData); 18JsonSchemaFactory schemaFactory = JsonSchemaFactory 19 .getInstance(VersionFlag.V7, 20 builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(schemas))); 21``` 22 23Schemas can be loaded through a function. 24 25```java 26String schemaData = "{\r\n" 27 + " \"type\": \"integer\"\r\n" 28 + "}"; 29Map<String, String> schemas = Collections.singletonMap("https://www.example.com/integer.json", schemaData); 30 JsonSchemaFactory schemaFactory = JsonSchemaFactory 31 .getInstance(VersionFlag.V7, 32 builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.schemas(schemas::get))); 33``` 34 35Schemas can also be loaded in the following manner. 36 37```java 38class RegistryEntry { 39 private final String schemaData; 40 41 public RegistryEntry(String schemaData) { 42 this.schemaData = schemaData; 43 } 44 45 public String getSchemaData() { 46 return this.schemaData; 47 } 48} 49 50String schemaData = "{\r\n" 51 + " \"type\": \"integer\"\r\n" 52 + "}"; 53Map<String, RegistryEntry> registry = Collections 54 .singletonMap("https://www.example.com/integer.json", new RegistryEntry(schemaData)); 55JsonSchemaFactory schemaFactory = JsonSchemaFactory 56 .getInstance(VersionFlag.V7, builder -> builder 57 .schemaLoaders(schemaLoaders -> schemaLoaders.schemas(registry::get, RegistryEntry::getSchemaData))); 58``` 59 60## Mapping Schema Identifier to Retrieval IRI 61 62The schema identifier can be mapped to the retrieval IRI by implementing the `SchemaMapper` interface. 63 64### Configuring Schema Mapper 65 66```java 67class CustomSchemaMapper implements SchemaMapper { 68 @Override 69 public AbsoluteIri map(AbsoluteIri absoluteIRI) { 70 String iri = absoluteIRI.toString(); 71 if ("https://www.example.com/integer.json".equals(iri)) { 72 return AbsoluteIri.of("classpath:schemas/integer.json"); 73 } 74 return null; 75 } 76} 77 78JsonSchemaFactory schemaFactory = JsonSchemaFactory 79 .getInstance(VersionFlag.V7, 80 builder -> builder.schemaMappers(schemaMappers -> schemaMappers.add(new CustomSchemaMapper()))); 81``` 82 83### Configuring Prefix Mappings 84 85```java 86JsonSchemaFactory schemaFactory = JsonSchemaFactory 87 .getInstance(VersionFlag.V7, 88 builder -> builder 89 .schemaMappers(schemaMappers -> schemaMappers 90 .mapPrefix("https://json-schema.org", "classpath:") 91 .mapPrefix("http://json-schema.org", "classpath:"))); 92``` 93 94### Configuring Mappings 95 96```java 97Map<String, String> mappings = Collections 98 .singletonMap("https://www.example.com/integer.json", "classpath:schemas/integer.json"); 99 100JsonSchemaFactory schemaFactory = JsonSchemaFactory 101 .getInstance(VersionFlag.V7, 102 builder -> builder.schemaMappers(schemaMappers -> schemaMappers.mappings(mappings))); 103``` 104 105## Customizing Network Schema Retrieval 106 107The 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](https://github.com/networknt/json-schema-validator/issues/240) 108 109### Configuring Custom URI Schema Loader 110 111The default `UriSchemaLoader` can be overwritten in order to customize its behaviour in regards of authorization or error handling. 112 113The `SchemaLoader` interface must implemented and the implementation configured on the `JsonSchemaFactory`. 114 115```java 116public class CustomUriSchemaLoader implements SchemaLoader { 117 private static final Logger LOGGER = LoggerFactory.getLogger(CustomUriSchemaLoader.class); 118 private final String authorizationToken; 119 private final HttpClient client; 120 121 public CustomUriSchemaLoader(String authorizationToken) { 122 this.authorizationToken = authorizationToken; 123 this.client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); 124 } 125 126 @Override 127 public InputStreamSource getSchema(AbsoluteIri absoluteIri) { 128 String scheme = absoluteIri.getScheme(); 129 if ("https".equals(scheme) || "http".equals(scheme)) { 130 URI uri = URI.create(absoluteIri.toString()); 131 return () -> { 132 HttpRequest request = HttpRequest.newBuilder().uri(uri).header("Authorization", authorizationToken).build(); 133 try { 134 HttpResponse<String> response = this.client.send(request, HttpResponse.BodyHandlers.ofString()); 135 if ((200 > response.statusCode()) || (response.statusCode() > 299)) { 136 String errorMessage = String.format("Could not get data from schema endpoint. The following status %d was returned.", response.statusCode()); 137 LOGGER.error(errorMessage); 138 } 139 return new ByteArrayInputStream(response.body().getBytes(StandardCharsets.UTF_8)); 140 } catch (InterruptedException e) { 141 throw new RuntimeException(e); 142 } 143 } 144 } 145 return null; 146 } 147} 148``` 149 150Within the `JsonSchemaFactory` the custom `SchemaLoader` must be configured. 151 152```java 153CustomUriSchemaLoader uriSchemaLoader = new CustomUriSchemaLoader(authorizationToken); 154 155JsonSchemaFactory schemaFactory = JsonSchemaFactory 156 .getInstance(VersionFlag.V7, 157 builder -> builder.schemaLoaders(schemaLoaders -> schemaLoaders.add(uriSchemaLoader))); 158``` 159