Skip to content

Commit ea8b9ad

Browse files
committed
making it possible to pre-define a schema by URI before schema loading
Reference: section 8.3.1 of json schema core draft-7 says: "Such URIs and schemas can be supplied to an implementation prior to processing instances" So here if the caller associates a raw schema to an URI using `SchemaLoaderBuilder#registerSchemaByURI()` then the loader will recognize this URI and load the associated raw schema without hitting the network or any external protocols. This was originally requested in #38.
1 parent d3ec64e commit ea8b9ad

File tree

10 files changed

+180
-23
lines changed

10 files changed

+180
-23
lines changed

core/src/main/java/org/everit/json/schema/loader/JsonPointerEvaluator.java

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import java.io.InputStreamReader;
1111
import java.io.UncheckedIOException;
1212
import java.io.UnsupportedEncodingException;
13-
import java.net.URI;
1413
import java.net.URISyntaxException;
1514
import java.net.URLDecoder;
1615
import java.nio.charset.Charset;
@@ -111,30 +110,22 @@ static final JsonPointerEvaluator forDocument(JsonObject document, String fragme
111110
private static JsonObject configureBasedOnState(JsonObject obj, LoadingState callingState, String id) {
112111
obj.ls = new LoadingState(callingState.config,
113112
callingState.pointerSchemas, obj, obj,
114-
validateURI(callingState, id),
113+
validateURI(callingState, id).asJavaURI(),
115114
SchemaLocation.empty());
116115
return obj;
117116
}
118117

119118
static final JsonPointerEvaluator forURL(SchemaClient schemaClient, String url, LoadingState callingState) {
120-
int poundIdx = url.indexOf('#');
121-
String fragment;
122-
String toBeQueried;
123-
if (poundIdx == -1) {
124-
toBeQueried = url;
125-
fragment = "";
126-
} else {
127-
fragment = url.substring(poundIdx);
128-
toBeQueried = url.substring(0, poundIdx);
129-
}
130-
validateURI(callingState, toBeQueried);
131-
return new JsonPointerEvaluator(() -> configureBasedOnState(executeWith(schemaClient, toBeQueried), callingState, toBeQueried),
132-
fragment);
119+
Uri uri = validateURI(callingState, url);
120+
return new JsonPointerEvaluator(
121+
() -> configureBasedOnState(executeWith(schemaClient, uri.toBeQueried.toString()), callingState,
122+
uri.toBeQueried.toString()),
123+
uri.fragment);
133124
}
134125

135-
private static URI validateURI(LoadingState callingState, String toBeQueried) {
126+
private static Uri validateURI(LoadingState callingState, String toBeQueried) {
136127
try {
137-
return new URI(toBeQueried);
128+
return Uri.parse(toBeQueried);
138129
} catch (URISyntaxException e) {
139130
throw callingState.createSchemaException(e);
140131
}

core/src/main/java/org/everit/json/schema/loader/LoaderConfig.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package org.everit.json.schema.loader;
22

3+
import static java.util.Collections.emptyMap;
34
import static java.util.Objects.requireNonNull;
45
import static org.everit.json.schema.loader.SpecificationVersion.DRAFT_4;
56

7+
import java.net.URI;
8+
import java.util.HashMap;
69
import java.util.Map;
710

811
import org.everit.json.schema.FormatValidator;
@@ -23,6 +26,8 @@ static LoaderConfig defaultV4Config() {
2326

2427
final Map<String, FormatValidator> formatValidators;
2528

29+
final Map<URI, Object> schemasByURI;
30+
2631
final SpecificationVersion specVersion;
2732

2833
final boolean useDefaults;
@@ -33,14 +38,20 @@ static LoaderConfig defaultV4Config() {
3338

3439
LoaderConfig(SchemaClient schemaClient, Map<String, FormatValidator> formatValidators,
3540
SpecificationVersion specVersion, boolean useDefaults) {
36-
this(schemaClient, formatValidators, specVersion, useDefaults, false, new JavaUtilRegexpFactory());
41+
this(schemaClient, formatValidators, emptyMap(), specVersion, useDefaults, false, new JavaUtilRegexpFactory());
3742
}
3843

3944
LoaderConfig(SchemaClient schemaClient, Map<String, FormatValidator> formatValidators,
45+
Map<URI, Object> schemasByURI,
4046
SpecificationVersion specVersion, boolean useDefaults, boolean nullableSupport,
4147
RegexpFactory regexpFactory) {
4248
this.schemaClient = requireNonNull(schemaClient, "schemaClient cannot be null");
4349
this.formatValidators = requireNonNull(formatValidators, "formatValidators cannot be null");
50+
if (schemasByURI == null) {
51+
this.schemasByURI = new HashMap<>();
52+
} else {
53+
this.schemasByURI = schemasByURI;
54+
}
4455
this.specVersion = requireNonNull(specVersion, "specVersion cannot be null");
4556
this.useDefaults = useDefaults;
4657
this.nullableSupport = nullableSupport;

core/src/main/java/org/everit/json/schema/loader/ReferenceLookup.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,16 +151,13 @@ Schema.Builder<?> lookup(String relPointerString, JsonObject ctx) {
151151
return refBuilder;
152152
}
153153

154-
boolean isInternal = isSameDocumentRef(absPointerString);
155-
JsonPointerEvaluator pointer = isInternal
156-
? JsonPointerEvaluator.forDocument(ls.rootSchemaJson(), absPointerString)
157-
: JsonPointerEvaluator.forURL(schemaClient, absPointerString, ls);
154+
JsonPointerEvaluator pointer = createPointerEvaluator(absPointerString);
158155
ReferenceSchema.Builder refBuilder = ReferenceSchema.builder()
159156
.refValue(relPointerString);
160157
ls.pointerSchemas.put(absPointerString, refBuilder);
161158
JsonPointerEvaluator.QueryResult result = pointer.query();
162159

163-
URI resolutionScope = !isInternal ? withoutFragment(absPointerString) : ls.id;
160+
URI resolutionScope = !isSameDocumentRef(absPointerString) ? withoutFragment(absPointerString) : ls.id;
164161
JsonObject containingDocument = result.getContainingDocument();
165162
SchemaLoader childLoader = ls.initChildLoader()
166163
.pointerToCurrentObj(SchemaLocation.parseURI(absPointerString))
@@ -173,6 +170,31 @@ Schema.Builder<?> lookup(String relPointerString, JsonObject ctx) {
173170
return refBuilder;
174171
}
175172

173+
private JsonObject initJsonObjectWithId(Object rawObject, URI id) {
174+
JsonObject o = JsonValue.of(ls.config.schemasByURI.get(id)).requireObject();
175+
new LoadingState(ls.config, ls.pointerSchemas, o, o, id, SchemaLocation.parseURI(id.toString()));
176+
return o;
177+
}
178+
179+
private JsonPointerEvaluator createPointerEvaluator(String absPointerString) {
180+
if (isSameDocumentRef(absPointerString)) {
181+
return JsonPointerEvaluator.forDocument(ls.rootSchemaJson(), absPointerString);
182+
}
183+
try {
184+
Uri uri = Uri.parse(absPointerString);
185+
if (ls.config.schemasByURI.containsKey(uri.asJavaURI())) {
186+
JsonObject o = initJsonObjectWithId(ls.config.schemasByURI.get(uri.asJavaURI()), uri.asJavaURI());
187+
return JsonPointerEvaluator.forDocument(o, "#");
188+
} else if (ls.config.schemasByURI.containsKey(uri.toBeQueried)) {
189+
JsonObject o = initJsonObjectWithId(ls.config.schemasByURI.get(uri.toBeQueried), uri.toBeQueried);
190+
return JsonPointerEvaluator.forDocument(o, uri.fragment);
191+
}
192+
} catch (URISyntaxException e) {
193+
throw ls.createSchemaException(e);
194+
}
195+
return JsonPointerEvaluator.forURL(schemaClient, absPointerString, ls);
196+
}
197+
176198
private boolean isSameDocumentRef(String ref) {
177199
return ref.startsWith("#");
178200
}

core/src/main/java/org/everit/json/schema/loader/SchemaLoader.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ public static class SchemaLoaderBuilder {
7171

7272
RegexpFactory regexpFactory = new JavaUtilRegexpFactory();
7373

74+
Map<URI, Object> schemasByURI = null;
75+
7476
public SchemaLoaderBuilder() {
7577
setSpecVersion(DRAFT_4);
7678
}
@@ -237,6 +239,14 @@ public SchemaLoaderBuilder regexpFactory(RegexpFactory regexpFactory) {
237239
this.regexpFactory = regexpFactory;
238240
return this;
239241
}
242+
243+
public SchemaLoaderBuilder registerSchemaByURI(URI uri, Object schema) {
244+
if (schemasByURI == null) {
245+
schemasByURI = new HashMap<>();
246+
}
247+
schemasByURI.put(uri, schema);
248+
return this;
249+
}
240250
}
241251

242252
public static SchemaLoaderBuilder builder() {
@@ -307,6 +317,7 @@ public SchemaLoader(SchemaLoaderBuilder builder) {
307317
}
308318
this.config = new LoaderConfig(builder.schemaClient,
309319
builder.formatValidators,
320+
builder.schemasByURI,
310321
specVersion,
311322
builder.useDefaults,
312323
builder.nullableSupport,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.everit.json.schema.loader;
2+
3+
import static java.util.Objects.requireNonNull;
4+
5+
import java.net.URI;
6+
import java.net.URISyntaxException;
7+
8+
class Uri {
9+
10+
static Uri parse(String str) throws URISyntaxException {
11+
URI rawUri = new URI(str);
12+
int poundIdx = str.indexOf('#');
13+
String fragment;
14+
URI toBeQueried;
15+
if (poundIdx == -1) {
16+
toBeQueried = rawUri;
17+
fragment = "";
18+
} else {
19+
fragment = str.substring(poundIdx);
20+
toBeQueried = new URI(str.substring(0, poundIdx));
21+
}
22+
return new Uri(toBeQueried, fragment);
23+
}
24+
25+
URI toBeQueried;
26+
27+
String fragment;
28+
29+
private Uri(URI toBeQueried, String fragment) {
30+
this.toBeQueried = requireNonNull(toBeQueried, "toBeQueried cannot be null");
31+
this.fragment = requireNonNull(fragment, "fragment cannot be null");
32+
}
33+
34+
URI asJavaURI() {
35+
try {
36+
return new URI(toBeQueried + fragment);
37+
} catch (URISyntaxException e) {
38+
throw new RuntimeException(e);
39+
}
40+
}
41+
42+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.everit.json.schema.loader;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.mockito.Mockito.mock;
5+
import static org.mockito.Mockito.verifyNoMoreInteractions;
6+
7+
import java.net.URI;
8+
import java.net.URISyntaxException;
9+
10+
import org.everit.json.schema.ReferenceSchema;
11+
import org.everit.json.schema.ResourceLoader;
12+
import org.junit.Test;
13+
14+
public class RegisteredURIResolutionTest {
15+
16+
private static final ResourceLoader LOADER = new ResourceLoader("/org/everit/jsonvalidator/registered-uris/");
17+
18+
@Test
19+
public void success() throws URISyntaxException {
20+
SchemaClient mock = mock(SchemaClient.class);
21+
SchemaLoader loader = SchemaLoader.builder()
22+
.schemaClient(mock)
23+
.schemaJson(LOADER.readObj("ref-urn.json"))
24+
.registerSchemaByURI(new URI("urn:uuid:a773c7a2-1a13-4f6a-a70d-694befe0ce63"), LOADER.readObj("schema-by-urn.json"))
25+
.build();
26+
27+
ReferenceSchema actual = (ReferenceSchema) loader.load().build();
28+
29+
assertEquals("schema-by-urn", actual.getReferredSchema().getTitle());
30+
verifyNoMoreInteractions(mock);
31+
}
32+
33+
@Test
34+
public void urnWithFragment() throws URISyntaxException {
35+
SchemaClient mock = mock(SchemaClient.class);
36+
SchemaLoader loader = SchemaLoader.builder()
37+
.schemaClient(mock)
38+
.schemaJson(LOADER.readObj("ref-urn-fragment.json"))
39+
.registerSchemaByURI(new URI("urn:uuid:a773c7a2-1a13-4f6a-a70d-694befe0ce63"), LOADER.readObj("schema-by-urn.json"))
40+
.build();
41+
42+
ReferenceSchema actual = (ReferenceSchema) loader.load().build();
43+
44+
assertEquals("subschema-by-urn", actual.getReferredSchema().getTitle());
45+
verifyNoMoreInteractions(mock);
46+
}
47+
48+
@Test
49+
public void httpURL() throws URISyntaxException {
50+
SchemaClient mock = mock(SchemaClient.class);
51+
SchemaLoader loader = SchemaLoader.builder()
52+
.schemaClient(mock)
53+
.schemaJson(LOADER.readObj("ref-example-org.json"))
54+
.registerSchemaByURI(new URI("http://example.org"), LOADER.readObj("schema-by-urn.json"))
55+
.build();
56+
57+
ReferenceSchema actual = (ReferenceSchema) loader.load().build();
58+
59+
assertEquals("schema-by-urn", actual.getReferredSchema().getTitle());
60+
verifyNoMoreInteractions(mock);
61+
}
62+
63+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"$ref": "http://example.org"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"$ref": "urn:uuid:a773c7a2-1a13-4f6a-a70d-694befe0ce63#/definitions/A"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"$ref": "urn:uuid:a773c7a2-1a13-4f6a-a70d-694befe0ce63"
3+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"title": "schema-by-urn",
3+
"definitions": {
4+
"A": {
5+
"title": "subschema-by-urn"
6+
}
7+
}
8+
}

0 commit comments

Comments
 (0)