Skip to content

Commit 9efce49

Browse files
committed
Support loading main schema and relative refs from URLs (#443)
Remove schemaRoot config, resolutionScope is now set to base path
1 parent 2a9cb32 commit 9efce49

File tree

2 files changed

+63
-25
lines changed

2 files changed

+63
-25
lines changed

metafacture-json/src/main/java/org/metafacture/json/JsonValidator.java

+26-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021, 2022 Fabian Steeg, hbz
2+
* Copyright 2021, 2023 Fabian Steeg, hbz
33
*
44
* Licensed under the Apache License, Version 2.0 the "License";
55
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
2828
import org.everit.json.schema.ValidationException;
2929
import org.everit.json.schema.loader.SchemaClient;
3030
import org.everit.json.schema.loader.SchemaLoader;
31+
import org.everit.json.schema.loader.SchemaLoader.SchemaLoaderBuilder;
3132
import org.json.JSONException;
3233
import org.json.JSONObject;
3334
import org.json.JSONTokener;
@@ -37,14 +38,14 @@
3738
import java.io.FileWriter;
3839
import java.io.IOException;
3940
import java.io.InputStream;
41+
import java.net.URL;
4042

4143
/**
4244
* Validate JSON against a given schema, pass only valid input to the receiver.
4345
*
4446
* @author Fabian Steeg (fsteeg)
4547
*/
4648
@Description("Validate JSON against a given schema, send only valid input to the receiver. Pass the schema location to validate against. " +
47-
"Set `schemaRoot` for resolving sub-schemas referenced in `$id` or `$ref` (defaults to the classpath root: `/`). " +
4849
"Write valid and/or invalid output to locations specified with `writeValid` and `writeInvalid`. " +
4950
"Set the JSON key for the record ID value with `idKey` (for logging output, defaults to `id`).")
5051
@In(String.class)
@@ -53,15 +54,13 @@
5354
public final class JsonValidator extends DefaultObjectPipe<String, ObjectReceiver<String>> {
5455

5556
private static final Logger LOG = LoggerFactory.getLogger(JsonValidator.class);
56-
private static final String DEFAULT_SCHEMA_ROOT = "/";
5757
private static final String DEFAULT_ID_KEY = "id";
5858
private String schemaUrl;
5959
private Schema schema;
6060
private long fail;
6161
private long success;
6262
private FileWriter writeInvalid;
6363
private FileWriter writeValid;
64-
private String schemaRoot = DEFAULT_SCHEMA_ROOT;
6564
private String idKey = DEFAULT_ID_KEY;
6665

6766
/**
@@ -71,13 +70,6 @@ public JsonValidator(final String url) {
7170
this.schemaUrl = url;
7271
}
7372

74-
/**
75-
* @param schemaRoot The root location for resolving sub-schemas referenced in '$id' or '$ref'.
76-
*/
77-
public void setSchemaRoot(final String schemaRoot) {
78-
this.schemaRoot = schemaRoot;
79-
}
80-
8173
/**
8274
* @param writeValid The location to write valid data to.
8375
*/
@@ -134,18 +126,34 @@ private void initSchema() {
134126
if (schema != null) {
135127
return;
136128
}
137-
try (InputStream inputStream = getClass().getResourceAsStream(schemaUrl)) {
138-
schema = SchemaLoader.builder()
139-
.schemaJson(new JSONObject(new JSONTokener(inputStream)))
140-
.schemaClient(SchemaClient.classPathAwareClient())
141-
.resolutionScope("classpath://" + schemaRoot)
142-
.build().load().build();
129+
SchemaLoaderBuilder schemaLoader = SchemaLoader.builder();
130+
try {
131+
final URL url = new URL(schemaUrl);
132+
schemaLoader = schemaLoader.schemaJson(jsonFrom(url.openStream()))
133+
.resolutionScope(baseFor(url.toString()));
134+
}
135+
catch (final IOException e) {
136+
LOG.info("Could not read as URL: {}, trying to load from class path", schemaUrl);
137+
schemaLoader = schemaLoader.schemaClient(SchemaClient.classPathAwareClient())
138+
.schemaJson(jsonFrom(getClass().getResourceAsStream(schemaUrl)))
139+
.resolutionScope("classpath://" + baseFor(schemaUrl));
140+
}
141+
schema = schemaLoader.build().load().build();
142+
}
143+
144+
private JSONObject jsonFrom(final InputStream inputStream) {
145+
try {
146+
return new JSONObject(new JSONTokener(inputStream));
143147
}
144-
catch (final IOException | JSONException e) {
148+
catch (final JSONException e) {
145149
throw new MetafactureException(e.getMessage(), e);
146150
}
147151
}
148152

153+
private String baseFor(final String path) {
154+
return path.substring(0, path.lastIndexOf('/') + 1);
155+
}
156+
149157
private FileWriter fileWriter(final String fileLocation) {
150158
try {
151159
return new FileWriter(fileLocation);

metafacture-json/src/test/java/org/metafacture/json/JsonValidatorTest.java

+37-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021, 2022 Fabian Steeg, hbz
2+
* Copyright 2021, 2023 Fabian Steeg, hbz
33
*
44
* Licensed under the Apache License, Version 2.0 the "License";
55
* you may not use this file except in compliance with the License.
@@ -18,19 +18,26 @@
1818
import static org.hamcrest.CoreMatchers.both;
1919
import static org.hamcrest.CoreMatchers.containsString;
2020
import static org.junit.Assert.assertThat;
21+
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
22+
import static com.github.tomakehurst.wiremock.client.WireMock.request;
2123

2224
import java.io.BufferedReader;
2325
import java.io.IOException;
2426
import java.io.InputStreamReader;
2527
import java.net.MalformedURLException;
2628
import java.net.URL;
2729
import java.nio.charset.StandardCharsets;
30+
import java.util.Arrays;
31+
import java.util.Collection;
32+
import java.util.function.Function;
2833
import java.util.stream.Collectors;
2934

3035
import org.junit.After;
3136
import org.junit.Before;
3237
import org.junit.Rule;
3338
import org.junit.Test;
39+
import org.junit.runner.RunWith;
40+
import org.junit.runners.Parameterized;
3441
import org.metafacture.framework.MetafactureException;
3542
import org.metafacture.framework.ObjectReceiver;
3643
import org.mockito.InOrder;
@@ -49,9 +56,11 @@
4956
* @author Fabian Steeg
5057
*
5158
*/
59+
@RunWith(Parameterized.class)
5260
public final class JsonValidatorTest {
5361

54-
private static final String SCHEMA = "/schemas/schema.json";
62+
private static final String MAIN_SCHEMA = "/schemas/schema.json";
63+
private static final String ID_SCHEMA = "/schemas/id.json";
5564
private static final String JSON_VALID = "{\"id\":\"http://example.org/\"}";
5665
private static final String JSON_INVALID_MISSING_REQUIRED = "{}";
5766
private static final String JSON_INVALID_URI_FORMAT= "{\"id\":\"example.org/\"}";
@@ -63,30 +72,51 @@ public final class JsonValidatorTest {
6372
@Mock
6473
private ObjectReceiver<String> receiver;
6574
private InOrder inOrder;
75+
private Function<Object, String> schemaLocationGetter;
6676

6777
@Rule
6878
public WireMockRule wireMockRule = new WireMockRule(WireMockConfiguration.wireMockConfig()
6979
.jettyAcceptors(Runtime.getRuntime().availableProcessors()).dynamicPort());
7080

81+
@Parameterized.Parameters(name = "{index}")
82+
public static Collection<Object[]> siteMaps() {
83+
return Arrays.asList((Object[][]) (new Function[][] { //
84+
// Pass the schema to each test as path on classpath, file url, and http url:
85+
{ (Object rule) -> MAIN_SCHEMA },
86+
{ (Object rule) -> JsonValidatorTest.class.getResource(MAIN_SCHEMA).toString() },
87+
{ (Object rule) -> ((WireMockRule) rule).baseUrl() + MAIN_SCHEMA } }));
88+
}
89+
90+
public JsonValidatorTest(Function<Object, String> schemaLocationGetter) {
91+
this.schemaLocationGetter = schemaLocationGetter;
92+
}
93+
7194
@Before
7295
public void setup() throws IOException {
7396
MockitoAnnotations.initMocks(this);
74-
WireMock.stubFor(WireMock.request("GET", WireMock.urlEqualTo("/schema"))
75-
.willReturn(WireMock.ok().withBody(readToString(getClass().getResource(SCHEMA)))));
76-
validator = new JsonValidator(SCHEMA);
77-
validator.setSchemaRoot("/schemas/");
97+
wireMock(MAIN_SCHEMA, ID_SCHEMA);
98+
String schemaLocation = schemaLocationGetter.apply(wireMockRule);
99+
validator = new JsonValidator(schemaLocation);
78100
validator.setReceiver(receiver);
79101
inOrder = Mockito.inOrder(receiver);
80102
}
81103

104+
private void wireMock(final String... schemaLocations) throws IOException {
105+
for (String schemaLocation : schemaLocations) {
106+
stubFor(request("GET", WireMock.urlEqualTo(schemaLocation)).willReturn(
107+
WireMock.ok().withBody(readToString(getClass().getResource(schemaLocation)))
108+
.withHeader("Content-type", "application/json")));
109+
}
110+
}
111+
82112
private String readToString(final URL url) throws IOException {
83113
return new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))
84114
.lines().collect(Collectors.joining("\n"));
85115
}
86116

87117
@Test
88118
public void callWireMockSchema() throws MalformedURLException, IOException {
89-
final String schemaContent = readToString(new URL(wireMockRule.baseUrl() + "/schema"));
119+
final String schemaContent = readToString(new URL(wireMockRule.baseUrl() + MAIN_SCHEMA));
90120
assertThat(schemaContent, both(containsString("$schema")).and(containsString("$ref")));
91121
}
92122

0 commit comments

Comments
 (0)