diff --git a/core/pom.xml b/core/pom.xml
index 9de7e9bdf8..c40a357443 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -169,6 +169,11 @@
oauth2-openid
1.2.0
+
+ com.networknt
+ json-schema-validator
+ 1.5.9
+
com.jayway.jsonpath
json-path
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java
index b0d038be08..f183ae349d 100644
--- a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/ValidatorInterceptor.java
@@ -17,6 +17,7 @@
import com.predic8.membrane.annot.*;
import com.predic8.membrane.core.exchange.*;
import com.predic8.membrane.core.interceptor.*;
+import com.predic8.membrane.core.interceptor.schemavalidation.json.*;
import com.predic8.membrane.core.proxies.*;
import com.predic8.membrane.core.resolver.*;
import com.predic8.membrane.core.util.*;
@@ -32,6 +33,7 @@
import static com.predic8.membrane.core.interceptor.Outcome.ABORT;
import static com.predic8.membrane.core.interceptor.Outcome.*;
import static com.predic8.membrane.core.resolver.ResolverMap.*;
+import static com.predic8.membrane.core.util.TextUtil.linkURL;
/**
* Basically switches over {@link WSDLValidator}, {@link XMLSchemaValidator},
@@ -49,6 +51,13 @@ public class ValidatorInterceptor extends AbstractInterceptor implements Applica
private String schema;
private String serviceName;
private String jsonSchema;
+
+ /**
+ * Schema version e.g. JSON Schema version 04, 07, 2020-12
+ * Could also be used for XML or WSDL schema versions later.
+ */
+ private String schemaVersion = "2020-12";
+
private String schematron;
private String failureHandler;
private boolean skipFaults;
@@ -89,7 +98,7 @@ private MessageValidator getMessageValidator() throws Exception {
return new XMLSchemaValidator(resourceResolver, combine(getBaseLocation(), schema), createFailureHandler());
}
if (jsonSchema != null) {
- return new JSONSchemaValidator(resourceResolver, combine(getBaseLocation(), jsonSchema), createFailureHandler());
+ return new JSONYAMLSchemaValidator(resourceResolver, combine(getBaseLocation(), jsonSchema), createFailureHandler(), schemaVersion);
}
if (schematron != null) {
return new SchematronValidator(combine(getBaseLocation(), schematron), createFailureHandler(), router, applicationContext);
@@ -206,6 +215,20 @@ public void setJsonSchema(String jsonSchema) {
this.jsonSchema = jsonSchema;
}
+ public String getSchemaVersion() {
+ return schemaVersion;
+ }
+
+ /**
+ * @description The version of the Schema.
+ * @example 04, 05, 06, 07, 2019-09, 2020-12
+ * @default 2020-12
+ */
+ @MCAttribute
+ public void setSchemaVersion(String version) {
+ this.schemaVersion = version;
+ }
+
public String getSchematron() {
return schematron;
}
@@ -263,19 +286,19 @@ public String getLongDescription() {
sb.append(" according to ");
if (wsdl != null) {
sb.append("the WSDL at
");
- sb.append(TextUtil.linkURL(wsdl));
+ sb.append(linkURL(wsdl));
}
if (schema != null) {
sb.append("the XML Schema at
");
- sb.append(TextUtil.linkURL(schema));
+ sb.append(linkURL(schema));
}
if (jsonSchema != null) {
sb.append("the JSON Schema at
");
- sb.append(TextUtil.linkURL(jsonSchema));
+ sb.append(linkURL(jsonSchema));
}
if (schematron != null) {
sb.append("the Schematron at
");
- sb.append(TextUtil.linkURL(schematron));
+ sb.append(linkURL(schematron));
}
sb.append(" .");
return sb.toString();
@@ -290,7 +313,7 @@ public interface FailureHandler {
private FailureHandler createFailureHandler() {
if (failureHandler == null || failureHandler.equals("response"))
- return null;
+ return (msg,exchange) -> {};
if (failureHandler.equals("log"))
return (message, exc) -> log.info("Validation failure: {}", message);
throw new IllegalArgumentException("Unknown failureHandler type: " + failureHandler);
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONSchemaVersionParser.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONSchemaVersionParser.java
new file mode 100644
index 0000000000..c5fe867ebc
--- /dev/null
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONSchemaVersionParser.java
@@ -0,0 +1,41 @@
+/* Copyright 2025 predic8 GmbH, www.predic8.com
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. */
+
+package com.predic8.membrane.core.interceptor.schemavalidation.json;
+
+import com.networknt.schema.*;
+import com.predic8.membrane.core.util.*;
+import org.jetbrains.annotations.*;
+
+import static com.networknt.schema.SchemaId.*;
+
+public class JSONSchemaVersionParser {
+
+ public static SpecVersion.VersionFlag parse(String version) {
+ return SpecVersion.VersionFlag.fromId(aliasToSpecId(version)).get();
+ }
+
+ static @NotNull String aliasToSpecId(String alias) {
+ if (alias == null)
+ throw new ConfigurationException("Unknown JSON Schema version: " + alias);
+ return switch (alias) {
+ case "04","draft-04" -> V4;
+ case "06","draft-06" -> V6;
+ case "07","draft-07" -> V7;
+ case "2019-09" -> V201909;
+ case "2020-12" -> V202012;
+ default -> throw new ConfigurationException("Unknown JSON Schema version: " + alias);
+ };
+ }
+}
diff --git a/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONYAMLSchemaValidator.java b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONYAMLSchemaValidator.java
new file mode 100644
index 0000000000..bcf89d2a47
--- /dev/null
+++ b/core/src/main/java/com/predic8/membrane/core/interceptor/schemavalidation/json/JSONYAMLSchemaValidator.java
@@ -0,0 +1,181 @@
+/* Copyright 2012 predic8 GmbH, www.predic8.com
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License. */
+
+package com.predic8.membrane.core.interceptor.schemavalidation.json;
+
+import com.github.fge.jsonschema.*;
+import com.networknt.schema.*;
+import com.predic8.membrane.core.exchange.*;
+import com.predic8.membrane.core.http.*;
+import com.predic8.membrane.core.interceptor.Interceptor.*;
+import com.predic8.membrane.core.interceptor.*;
+import com.predic8.membrane.core.interceptor.schemavalidation.*;
+import com.predic8.membrane.core.interceptor.schemavalidation.ValidatorInterceptor.*;
+import com.predic8.membrane.core.resolver.*;
+import org.jetbrains.annotations.*;
+import org.slf4j.*;
+
+import java.nio.charset.*;
+import java.util.*;
+import java.util.concurrent.atomic.*;
+
+import static com.networknt.schema.InputFormat.JSON;
+import static com.predic8.membrane.core.exceptions.ProblemDetails.*;
+import static com.predic8.membrane.core.interceptor.Outcome.*;
+import static java.nio.charset.StandardCharsets.*;
+
+public class JSONYAMLSchemaValidator extends AbstractMessageValidator {
+
+ private static final Logger log = LoggerFactory.getLogger(JSONYAMLSchemaValidator.class);
+
+ private final Resolver resolver;
+ private final String jsonSchema;
+ private final FailureHandler failureHandler;
+
+ private final AtomicLong valid = new AtomicLong();
+ private final AtomicLong invalid = new AtomicLong();
+ private final SpecVersion.VersionFlag schemaId;
+
+ /**
+ * JsonSchemaFactory instances are thread-safe provided its configuration is not modified.
+ */
+ JsonSchemaFactory jsonSchemaFactory;
+
+ SchemaValidatorsConfig config;
+
+ /**
+ * JsonSchema instances are thread-safe provided its configuration is not modified.
+ */
+ JsonSchema schema;
+
+ public JSONYAMLSchemaValidator(Resolver resolver, String jsonSchema, FailureHandler failureHandler, String schemaVersion) {
+ this.resolver = resolver;
+ this.jsonSchema = jsonSchema;
+ this.failureHandler = failureHandler;
+ this.schemaId = JSONSchemaVersionParser.parse( schemaVersion);
+ }
+
+ public JSONYAMLSchemaValidator(Resolver resolver, String jsonSchema, FailureHandler failureHandler) {
+ this(resolver, jsonSchema, failureHandler, "2020-12");
+ }
+
+ @Override
+ public String getName() {
+ return "JSON Schema Validator";
+ }
+
+ @Override
+ public void init() {
+ super.init();
+
+ jsonSchemaFactory = JsonSchemaFactory.getInstance(schemaId, builder ->
+ builder.schemaLoaders(loaders -> loaders.add(new MembraneSchemaLoader(resolver)))
+ // builder.schemaMappers(schemaMappers -> schemaMappers.mapPrefix("https://www.example.org/", "classpath:/"))
+ );
+
+ SchemaValidatorsConfig.Builder builder = SchemaValidatorsConfig.builder();
+ // By default the JDK regular expression implementation which is not ECMA 262 compliant is used
+ // Note that setting this requires including optional dependencies
+ // builder.regularExpressionFactory(GraalJSRegularExpressionFactory.getInstance());
+ // builder.regularExpressionFactory(JoniRegularExpressionFactory.getInstance());
+ config = builder.build();
+
+ // If the schema data does not specify an $id the absolute IRI of the schema location will be used as the $id.
+ schema= jsonSchemaFactory.getSchema(SchemaLocation.of( jsonSchema), config);
+ schema.initializeValidators();
+
+ }
+
+ public Outcome validateMessage(Exchange exc, Flow flow) throws Exception {
+ return validateMessage(exc, flow, UTF_8);
+ }
+
+ public Outcome validateMessage(Exchange exc, Flow flow, Charset ignored) throws Exception {
+
+ Set assertions = schema.validate(exc.getMessage(flow).getBodyAsStringDecoded(), JSON);
+
+ if (assertions.isEmpty()) {
+ valid.incrementAndGet();
+ return CONTINUE;
+ }
+ invalid.incrementAndGet();
+
+
+ log.debug("Validation failed: {}", assertions);
+
+ List