diff --git a/components/camel-everit-json-schema/src/main/docs/json-validator-component.adoc b/components/camel-everit-json-schema/src/main/docs/json-validator-component.adoc index 7097e04adc932..9bcca3db5f087 100644 --- a/components/camel-everit-json-schema/src/main/docs/json-validator-component.adoc +++ b/components/camel-everit-json-schema/src/main/docs/json-validator-component.adoc @@ -1,9 +1,7 @@ == JSON Schema Validator Component === Everit Json Schema Validator Component -*Available as of Camel version * - -*Available as of Camel version 2.20* +*Available as of Camel version 2.21* The JSON Schema Validator component performs bean validation of the message body agains JSON Schemas using the Everit.org JSON Schema library @@ -57,14 +55,15 @@ with the following path and query parameters: [width="100%",cols="2,5,^1,2",options="header"] |=== | Name | Description | Default | Type -| *resourceUri* | *Required* URL to a local resource on the classpath or a reference to lookup a bean in the Registry or a full URL to a remote resource or resource on the file system which contains the JSON Schema to validate against. | | String +| *resourceUri* | *Required* Path to the resource. You can prefix with: classpath file http ref or bean. classpath file and http loads the resource using these protocols (classpath is default). ref will lookup the resource in the registry. bean will call a method on a bean to be used as the resource. For bean you can specify the method name after dot eg bean:myBean.myMethod. | | String |=== -==== Query Parameters (6 parameters): +==== Query Parameters (7 parameters): [width="100%",cols="2,5,^1,2",options="header"] |=== | Name | Description | Default | Type +| *contentCache* (producer) | Sets whether to use resource content cache or not | false | boolean | *failOnNullBody* (producer) | Whether to fail if no body exists. | true | boolean | *failOnNullHeader* (producer) | Whether to fail if no header exists when validating against a header. | true | boolean | *headerName* (producer) | To validate against a header instead of the message body. | | String diff --git a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/DefaultJsonSchemaLoader.java b/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/DefaultJsonSchemaLoader.java index 8548f6e4205e2..605fcd0839167 100644 --- a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/DefaultJsonSchemaLoader.java +++ b/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/DefaultJsonSchemaLoader.java @@ -20,7 +20,6 @@ import java.io.InputStream; import org.apache.camel.CamelContext; -import org.apache.camel.util.ResourceHelper; import org.everit.json.schema.Schema; import org.everit.json.schema.loader.SchemaLoader; import org.everit.json.schema.loader.SchemaLoader.SchemaLoaderBuilder; @@ -32,11 +31,11 @@ public class DefaultJsonSchemaLoader implements JsonSchemaLoader { @Override - public Schema createSchema(CamelContext camelContext, String resourceUri) throws IOException { + public Schema createSchema(CamelContext camelContext, InputStream schemaInputStream) throws IOException { SchemaLoaderBuilder schemaLoaderBuilder = SchemaLoader.builder().draftV6Support(); - try (InputStream inputStream = ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext, resourceUri)) { + try (InputStream inputStream = schemaInputStream) { JSONObject rawSchema = new JSONObject(new JSONTokener(inputStream)); return schemaLoaderBuilder.schemaJson(rawSchema).build().load().build(); } diff --git a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaLoader.java b/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaLoader.java index bd7998bbfcb0c..4bab6a8b03ef0 100644 --- a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaLoader.java +++ b/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaLoader.java @@ -16,13 +16,29 @@ */ package org.apache.camel.component.everit.jsonschema; -import java.io.IOException; +import java.io.InputStream; import org.apache.camel.CamelContext; +import org.everit.json.schema.FormatValidator; import org.everit.json.schema.Schema; +/** + * Can be used to create custom schema for the JSON validator endpoint. + * This interface is useful to add custom {@link FormatValidator} to the {@link Schema} + * + * For more information see + * Format Validators + * in the Everit JSON Schema documentation. + */ public interface JsonSchemaLoader { - Schema createSchema(CamelContext camelContext, String resourceUri) throws IOException; + /** + * Create a new Schema based on the schema input stream. + * @param camelContext camel context + * @param schemaInputStream the resource input stream + * @return a Schema to be used when validating incoming requests + * @throws Exception if + */ + Schema createSchema(CamelContext camelContext, InputStream schemaInputStream) throws Exception; } diff --git a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaReader.java b/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaReader.java deleted file mode 100644 index 2876a07cea650..0000000000000 --- a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaReader.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.camel.component.everit.jsonschema; - -import java.io.IOException; - -import org.apache.camel.CamelContext; -import org.apache.camel.util.ObjectHelper; -import org.everit.json.schema.Schema; - -public class JsonSchemaReader { - private Schema schema; - - private final CamelContext camelContext; - private final String resourceUri; - private final JsonSchemaLoader schemaLoader; - - public JsonSchemaReader(CamelContext camelContext, String resourceUri, JsonSchemaLoader schemaLoader) { - ObjectHelper.notNull(camelContext, "camelContext"); - ObjectHelper.notNull(resourceUri, "resourceUri"); - ObjectHelper.notNull(schemaLoader, "schemaLoader"); - - this.camelContext = camelContext; - this.resourceUri = resourceUri; - this.schemaLoader = schemaLoader; - } - - public Schema getSchema() throws IOException { - if (this.schema == null) { - this.schema = this.schemaLoader.createSchema(this.camelContext, this.resourceUri); - } - return schema; - } - - public void setSchema(Schema schema) { - this.schema = schema; - } -} diff --git a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaValidatorEndpoint.java b/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaValidatorEndpoint.java index dd05a33cb4b89..80d1d32a981a1 100644 --- a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaValidatorEndpoint.java +++ b/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaValidatorEndpoint.java @@ -16,17 +16,26 @@ */ package org.apache.camel.component.everit.jsonschema; +import java.io.IOException; +import java.io.InputStream; + import org.apache.camel.Component; -import org.apache.camel.Consumer; -import org.apache.camel.Processor; -import org.apache.camel.Producer; -import org.apache.camel.api.management.ManagedOperation; +import org.apache.camel.Exchange; +import org.apache.camel.ExchangePattern; import org.apache.camel.api.management.ManagedResource; -import org.apache.camel.impl.DefaultEndpoint; -import org.apache.camel.spi.Metadata; +import org.apache.camel.component.ResourceEndpoint; import org.apache.camel.spi.UriEndpoint; import org.apache.camel.spi.UriParam; -import org.apache.camel.spi.UriPath; +import org.apache.camel.util.IOHelper; +import org.everit.json.schema.ObjectSchema; +import org.everit.json.schema.Schema; +import org.everit.json.schema.ValidationException; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -34,12 +43,10 @@ */ @ManagedResource(description = "Managed JSON ValidatorEndpoint") @UriEndpoint(scheme = "json-validator", title = "JSON Schema Validator", syntax = "json-validator:resourceUri", producerOnly = true, label = "core,validation") -public class JsonSchemaValidatorEndpoint extends DefaultEndpoint { +public class JsonSchemaValidatorEndpoint extends ResourceEndpoint { - @UriPath(description = "URL to a local resource on the classpath, or a reference to lookup a bean in the Registry," - + " or a full URL to a remote resource or resource on the file system which contains the JSON Schema to validate against.") - @Metadata(required = "true") - private String resourceUri; + private static final Logger LOG = LoggerFactory.getLogger(JsonSchemaValidatorEndpoint.class); + @UriParam(label = "advanced", description = "To use a custom org.apache.camel.component.everit.jsonschema.JsonValidatorErrorHandler. " + "The default error handler captures the errors and throws an exception.") private JsonValidatorErrorHandler errorHandler = new DefaultJsonValidationErrorHandler(); @@ -52,68 +59,93 @@ public class JsonSchemaValidatorEndpoint extends DefaultEndpoint { @UriParam(description = "To validate against a header instead of the message body.") private String headerName; - - /** - * We need a one-to-one relation between endpoint and a JsonSchemaReader - * to be able to clear the cached schema. See method - * {@link #clearCachedSchema}. - */ - private JsonSchemaReader schemaReader; - + private Schema schema; + public JsonSchemaValidatorEndpoint(String endpointUri, Component component, String resourceUri) { - super(endpointUri, component); - this.resourceUri = resourceUri; + super(endpointUri, component, resourceUri); } + @Override + public void clearContentCache() { + this.schema = null; + super.clearContentCache(); + } - @ManagedOperation(description = "Clears the cached schema, forcing to re-load the schema on next request") - public void clearCachedSchema() { - this.schemaReader.setSchema(null); // will cause to reload the schema + @Override + public ExchangePattern getExchangePattern() { + return ExchangePattern.InOut; } @Override - public Producer createProducer() throws Exception { - if (this.schemaReader == null) { - this.schemaReader = new JsonSchemaReader(getCamelContext(), resourceUri, schemaLoader); - // Load the schema once when creating the producer to fail fast if the schema is invalid. - this.schemaReader.getSchema(); + protected void onExchange(Exchange exchange) throws Exception { + Object jsonPayload = null; + InputStream is = null; + // Get a local copy of the current schema to improve concurrency. + Schema localSchema = this.schema; + if (localSchema == null) { + localSchema = getOrCreateSchema(); + } + try { + is = getContentToValidate(exchange, InputStream.class); + if (shouldUseHeader()) { + if (is == null && isFailOnNullHeader()) { + throw new NoJsonHeaderValidationException(exchange, headerName); + } + } else { + if (is == null && isFailOnNullBody()) { + throw new NoJsonBodyValidationException(exchange); + } + } + if (is != null) { + if (schema instanceof ObjectSchema) { + jsonPayload = new JSONObject(new JSONTokener(is)); + } else { + jsonPayload = new JSONArray(new JSONTokener(is)); + } + // throws a ValidationException if this object is invalid + schema.validate(jsonPayload); + LOG.debug("JSON is valid"); + } + } catch (ValidationException e) { + this.errorHandler.handleErrors(exchange, schema, e); + } catch (JSONException e) { + this.errorHandler.handleErrors(exchange, schema, e); + } finally { + IOHelper.close(is); } - JsonValidatingProcessor validator = new JsonValidatingProcessor(this.schemaReader); - configureValidator(validator); - - return new JsonSchemaValidatorProducer(this, validator); - } - - private void configureValidator(JsonValidatingProcessor validator) { - validator.setErrorHandler(errorHandler); - validator.setFailOnNullBody(failOnNullBody); - validator.setFailOnNullHeader(failOnNullHeader); - validator.setHeaderName(headerName); } - - @Override - public Consumer createConsumer(Processor processor) throws Exception { - throw new UnsupportedOperationException("Cannot consume from validator"); + + private T getContentToValidate(Exchange exchange, Class clazz) { + if (shouldUseHeader()) { + return exchange.getIn().getHeader(headerName, clazz); + } else { + return exchange.getIn().getBody(clazz); + } } - @Override - public boolean isSingleton() { - return true; + private boolean shouldUseHeader() { + return headerName != null; } - - public String getResourceUri() { - return resourceUri; - } - /** - * URL to a local resource on the classpath, or a reference to lookup a bean in the Registry, - * or a full URL to a remote resource or resource on the file system which contains the JSON Schema to validate against. + * Synchronized method to create a schema if is does not already exist. + * + * @return The currently loaded schema + * @throws IOException */ - public void setResourceUri(String resourceUri) { - this.resourceUri = resourceUri; + private Schema getOrCreateSchema() throws Exception { + synchronized (this) { + if (this.schema == null) { + this.schema = this.schemaLoader.createSchema(getCamelContext(), this.getResourceAsInputStream()); + } + } + return this.schema; } + @Override + protected String createEndpointUri() { + return "json-validator:" + getResourceUri(); + } public JsonValidatorErrorHandler getErrorHandler() { return errorHandler; diff --git a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaValidatorProducer.java b/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaValidatorProducer.java deleted file mode 100644 index b7efbee65f999..0000000000000 --- a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonSchemaValidatorProducer.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.camel.component.everit.jsonschema; - -import org.apache.camel.AsyncCallback; -import org.apache.camel.Endpoint; -import org.apache.camel.Exchange; -import org.apache.camel.impl.DefaultAsyncProducer; -import org.apache.camel.util.ServiceHelper; - -public class JsonSchemaValidatorProducer extends DefaultAsyncProducer { - - private final JsonValidatingProcessor validatingProcessor; - - public JsonSchemaValidatorProducer(Endpoint endpoint, JsonValidatingProcessor validatingProcessor) { - super(endpoint); - this.validatingProcessor = validatingProcessor; - } - - @Override - public boolean process(Exchange exchange, AsyncCallback callback) { - return validatingProcessor.process(exchange, callback); - } - - @Override - protected void doStart() throws Exception { - super.doStart(); - ServiceHelper.startService(validatingProcessor); - } - - @Override - protected void doStop() throws Exception { - super.doStop(); - ServiceHelper.stopService(validatingProcessor); - } - - @Override - protected void doShutdown() throws Exception { - super.doStop(); - ServiceHelper.stopAndShutdownService(validatingProcessor); - } -} diff --git a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonValidatingProcessor.java b/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonValidatingProcessor.java deleted file mode 100644 index 68cffafca3f2e..0000000000000 --- a/components/camel-everit-json-schema/src/main/java/org/apache/camel/component/everit/jsonschema/JsonValidatingProcessor.java +++ /dev/null @@ -1,152 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.camel.component.everit.jsonschema; - -import java.io.InputStream; - -import org.apache.camel.AsyncCallback; -import org.apache.camel.AsyncProcessor; -import org.apache.camel.Exchange; -import org.apache.camel.util.AsyncProcessorHelper; -import org.apache.camel.util.IOHelper; -import org.everit.json.schema.ObjectSchema; -import org.everit.json.schema.Schema; -import org.everit.json.schema.ValidationException; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.json.JSONTokener; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * A processor which validates the JSON of the inbound message body - * against some JSON schema - */ -public class JsonValidatingProcessor implements AsyncProcessor { - private static final Logger LOG = LoggerFactory.getLogger(JsonValidatingProcessor.class); - private JsonSchemaReader schemaReader; - private JsonValidatorErrorHandler errorHandler = new DefaultJsonValidationErrorHandler(); - private boolean failOnNullBody = true; - private boolean failOnNullHeader = true; - private String headerName; - - public JsonValidatingProcessor() { - - } - - public JsonValidatingProcessor(JsonSchemaReader schemaReader) { - this.schemaReader = schemaReader; - } - - public void process(Exchange exchange) throws Exception { - AsyncProcessorHelper.process(this, exchange); - } - - public boolean process(Exchange exchange, AsyncCallback callback) { - try { - doProcess(exchange); - } catch (Exception e) { - exchange.setException(e); - } - callback.done(true); - return true; - } - - protected void doProcess(Exchange exchange) throws Exception { - Object jsonPayload = null; - InputStream is = null; - Schema schema = null; - try { - is = getContentToValidate(exchange, InputStream.class); - if (shouldUseHeader()) { - if (is == null && isFailOnNullHeader()) { - throw new NoJsonHeaderValidationException(exchange, headerName); - } - } else { - if (is == null && isFailOnNullBody()) { - throw new NoJsonBodyValidationException(exchange); - } - } - if (is != null) { - schema = this.schemaReader.getSchema(); - if (schema instanceof ObjectSchema) { - jsonPayload = new JSONObject(new JSONTokener(is)); - } else { - jsonPayload = new JSONArray(new JSONTokener(is)); - } - // throws a ValidationException if this object is invalid - schema.validate(jsonPayload); - LOG.debug("JSON is valid"); - } - } catch (ValidationException e) { - this.errorHandler.handleErrors(exchange, schema, e); - } catch (JSONException e) { - this.errorHandler.handleErrors(exchange, schema, e); - } finally { - IOHelper.close(is); - } - } - - private T getContentToValidate(Exchange exchange, Class clazz) { - if (shouldUseHeader()) { - return exchange.getIn().getHeader(headerName, clazz); - } else { - return exchange.getIn().getBody(clazz); - } - } - - private boolean shouldUseHeader() { - return headerName != null; - } - - // Properties - // ----------------------------------------------------------------------- - - - public JsonValidatorErrorHandler getErrorHandler() { - return errorHandler; - } - - public void setErrorHandler(JsonValidatorErrorHandler errorHandler) { - this.errorHandler = errorHandler; - } - - public boolean isFailOnNullBody() { - return failOnNullBody; - } - - public void setFailOnNullBody(boolean failOnNullBody) { - this.failOnNullBody = failOnNullBody; - } - - public boolean isFailOnNullHeader() { - return failOnNullHeader; - } - - public void setFailOnNullHeader(boolean failOnNullHeader) { - this.failOnNullHeader = failOnNullHeader; - } - - public String getHeaderName() { - return headerName; - } - - public void setHeaderName(String headerName) { - this.headerName = headerName; - } -} diff --git a/components/camel-everit-json-schema/src/test/java/org/apache/camel/component/everit/jsonschema/TestCustomSchemaLoader.java b/components/camel-everit-json-schema/src/test/java/org/apache/camel/component/everit/jsonschema/TestCustomSchemaLoader.java index 7902fc3d50a28..1bd9260579ab0 100644 --- a/components/camel-everit-json-schema/src/test/java/org/apache/camel/component/everit/jsonschema/TestCustomSchemaLoader.java +++ b/components/camel-everit-json-schema/src/test/java/org/apache/camel/component/everit/jsonschema/TestCustomSchemaLoader.java @@ -4,7 +4,6 @@ import java.io.InputStream; import org.apache.camel.CamelContext; -import org.apache.camel.util.ResourceHelper; import org.everit.json.schema.Schema; import org.everit.json.schema.loader.SchemaLoader; import org.everit.json.schema.loader.SchemaLoader.SchemaLoaderBuilder; @@ -14,12 +13,12 @@ public class TestCustomSchemaLoader implements JsonSchemaLoader { @Override - public Schema createSchema(CamelContext camelContext, String resourceUri) + public Schema createSchema(CamelContext camelContext, InputStream schemaInputStream) throws IOException { SchemaLoaderBuilder schemaLoaderBuilder = SchemaLoader.builder().draftV6Support(); - try (InputStream inputStream = ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext, resourceUri)) { + try (InputStream inputStream = schemaInputStream) { JSONObject rawSchema = new JSONObject(new JSONTokener(inputStream)); return schemaLoaderBuilder .schemaJson(rawSchema) diff --git a/components/camel-everit-json-schema/src/test/resources/org/apache/camel/component/everit/jsonschema/schema.json b/components/camel-everit-json-schema/src/test/resources/org/apache/camel/component/everit/jsonschema/schema.json index 6fa28c3950b30..021640d594d62 100644 --- a/components/camel-everit-json-schema/src/test/resources/org/apache/camel/component/everit/jsonschema/schema.json +++ b/components/camel-everit-json-schema/src/test/resources/org/apache/camel/component/everit/jsonschema/schema.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "definitions": {}, "id": "http://example.com/example.json", "properties": { diff --git a/components/camel-everit-json-schema/src/test/resources/org/apache/camel/component/everit/jsonschema/schemawithformat.json b/components/camel-everit-json-schema/src/test/resources/org/apache/camel/component/everit/jsonschema/schemawithformat.json index 17ba7ad78d15e..a365115430ac5 100644 --- a/components/camel-everit-json-schema/src/test/resources/org/apache/camel/component/everit/jsonschema/schemawithformat.json +++ b/components/camel-everit-json-schema/src/test/resources/org/apache/camel/component/everit/jsonschema/schemawithformat.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-06/schema#", "definitions": {}, "id": "http://example.com/example.json", "properties": {