Skip to content

Commit 1da3eed

Browse files
committed
Initial commit...
0 parents  commit 1da3eed

13 files changed

+405
-0
lines changed

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
custom: https://paypal.me/janloebel

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Maven
2+
target
3+
4+
# Eclipse
5+
.settings/
6+
.classpath
7+
.project
8+
.factorypath
9+
10+
# IntelliJ
11+
*.iml

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 Jan Löbel
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Json-Schema-Validation-Starter
2+
3+
This provides a Spring-Boot-Starter to include JsonSchemaValidation with the help of the [https://github.com/networknt/json-schema-validator](https://github.com/networknt/json-schema-validator) -library.
4+
5+
## Usage
6+
7+
Include the starter into you're project.
8+
9+
You need to add jitpack to your `pom.xml` because this project is not available in the official maven repository.
10+
```
11+
<repositories>
12+
<repository>
13+
<id>jitpack.io</id>
14+
<url>https://jitpack.io</url>
15+
</repository>
16+
</repositories>
17+
```
18+
19+
Add the `json-schema-validation-starter`-dependency to your `pom.xml`
20+
```
21+
<dependency>
22+
<groupId>com.github.JanLoebel</groupId>
23+
<artifactId>json-schema-validation-starter</artifactId>
24+
<version>1.0.0</version>
25+
</dependency>
26+
```
27+
28+
After that simply create a json-schema and put it into e.g.: `resources/jsonschema/book.json`.
29+
The last step is now to let your entity know that it should be validated and which schema it should use.
30+
31+
```
32+
@JsonSchemaValidation("classpath:jsonschema/book.json")
33+
public class Book {
34+
private String title;
35+
private String author;
36+
}
37+
```
38+
39+
## Example project
40+
Head over to [http://github.com/JanLoebel/json-schema-validation-starter-example](http://github.com/JanLoebel/json-schema-validation-starter-example) to checkout the sample project.
41+
42+
## Contribution
43+
Please feel free to improve or modify the code and open a Pull-Request! Any contribution is welcome :)
44+
45+
## License
46+
MIT License
47+
48+
Copyright (c) 2019 Jan Löbel
49+
50+
See LICENSE file for details.

pom.xml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>com.github.JanLoebel</groupId>
7+
<artifactId>json-schema-validation-starter</artifactId>
8+
<version>1.0.0-SNAPSHOT</version>
9+
10+
<properties>
11+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
12+
<spring-boot.version>2.2.2.RELEASE</spring-boot.version>
13+
<spring.version>5.2.2.RELEASE</spring.version>
14+
<java.version>8</java.version>
15+
</properties>
16+
17+
<dependencies>
18+
<dependency>
19+
<groupId>org.springframework.boot</groupId>
20+
<artifactId>spring-boot</artifactId>
21+
<version>${spring-boot.version}</version>
22+
</dependency>
23+
<dependency>
24+
<groupId>org.springframework.boot</groupId>
25+
<artifactId>spring-boot-autoconfigure</artifactId>
26+
<version>${spring-boot.version}</version>
27+
</dependency>
28+
<dependency>
29+
<groupId>org.springframework</groupId>
30+
<artifactId>spring-webmvc</artifactId>
31+
<version>${spring.version}</version>
32+
</dependency>
33+
<dependency>
34+
<groupId>com.networknt</groupId>
35+
<artifactId>json-schema-validator</artifactId>
36+
<version>1.0.29</version>
37+
</dependency>
38+
</dependencies>
39+
40+
<build>
41+
<plugins>
42+
<plugin>
43+
<groupId>org.apache.maven.plugins</groupId>
44+
<artifactId>maven-compiler-plugin</artifactId>
45+
<configuration>
46+
<source>${java.version}</source>
47+
<target>${java.version}</target>
48+
</configuration>
49+
</plugin>
50+
</plugins>
51+
</build>
52+
</project>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.github.JanLoebel.jsonschemavalidation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.TYPE)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface JsonSchemaValidation {
11+
12+
String value();
13+
14+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.github.JanLoebel.jsonschemavalidation;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.github.JanLoebel.jsonschemavalidation.advice.JsonValidationRequestBodyControllerAdvice;
5+
import com.github.JanLoebel.jsonschemavalidation.provider.DefaultJsonSchemaProvider;
6+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
10+
@Configuration
11+
public class JsonSchemaValidationAutoConfiguration {
12+
13+
@Bean
14+
@ConditionalOnMissingBean
15+
public JsonValidationRequestBodyControllerAdvice jsonValidationRequestBodyControllerAdvice(ObjectMapper objectMapper) {
16+
return new JsonValidationRequestBodyControllerAdvice(objectMapper, new DefaultJsonSchemaProvider());
17+
}
18+
19+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.github.JanLoebel.jsonschemavalidation;
2+
3+
import com.networknt.schema.ValidationMessage;
4+
import org.springframework.http.HttpStatus;
5+
import org.springframework.web.server.ResponseStatusException;
6+
7+
import java.util.Collection;
8+
import java.util.stream.Collectors;
9+
10+
public class JsonSchemaValidationException extends ResponseStatusException {
11+
12+
private final Collection<ValidationMessage> validationMessages;
13+
14+
public JsonSchemaValidationException(Collection<ValidationMessage> validationMessages) {
15+
super(HttpStatus.BAD_REQUEST, buildMessage(validationMessages));
16+
this.validationMessages = validationMessages;
17+
}
18+
19+
private static String buildMessage(Collection<ValidationMessage> validationMessages) {
20+
final String prefix = "JsonSchemaValidation failed: ";
21+
return prefix + validationMessages
22+
.stream()
23+
.map(ValidationMessage::toString)
24+
.collect(Collectors.joining(", "));
25+
}
26+
27+
public Collection<ValidationMessage> getValidationMessages() {
28+
return this.validationMessages;
29+
}
30+
31+
@Override
32+
public String toString() {
33+
return "JsonSchemaValidationException{" +
34+
"validationMessages=" + validationMessages +
35+
'}';
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.github.JanLoebel.jsonschemavalidation.advice;
2+
3+
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import com.github.JanLoebel.jsonschemavalidation.JsonSchemaValidation;
7+
import com.github.JanLoebel.jsonschemavalidation.provider.JsonSchemaProvider;
8+
import com.networknt.schema.ValidationMessage;
9+
import org.springframework.core.MethodParameter;
10+
import org.springframework.http.HttpHeaders;
11+
import org.springframework.http.HttpInputMessage;
12+
import org.springframework.http.converter.HttpMessageConverter;
13+
import org.springframework.util.StreamUtils;
14+
import org.springframework.web.bind.annotation.ControllerAdvice;
15+
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
16+
17+
import java.io.ByteArrayInputStream;
18+
import java.io.IOException;
19+
import java.io.InputStream;
20+
import java.lang.reflect.Type;
21+
import java.nio.charset.StandardCharsets;
22+
import java.util.Set;
23+
24+
@ControllerAdvice
25+
public class JsonValidationRequestBodyControllerAdvice implements RequestBodyAdvice {
26+
27+
private ObjectMapper objectMapper;
28+
private JsonSchemaProvider jsonSchemaProvider;
29+
30+
public JsonValidationRequestBodyControllerAdvice(ObjectMapper objectMapper, JsonSchemaProvider jsonSchemaProvider) {
31+
this.objectMapper = objectMapper;
32+
this.jsonSchemaProvider = jsonSchemaProvider;
33+
}
34+
35+
@Override
36+
public boolean supports(MethodParameter parameter,
37+
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
38+
return parameter.getNestedParameterType().isAnnotationPresent(JsonSchemaValidation.class);
39+
}
40+
41+
@Override
42+
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
43+
Type targetType, Class<? extends HttpMessageConverter<?>> converterType)
44+
throws IOException {
45+
final String body = toString(inputMessage);
46+
47+
final String uri = extractSchemaUri(parameter);
48+
final JsonNode jsonNode = this.objectMapper.readTree(body);
49+
final Set<ValidationMessage> validationMessages = this.jsonSchemaProvider.loadSchema(uri).validate(jsonNode);
50+
this.jsonSchemaProvider.handleValidationMessages(validationMessages);
51+
52+
return buildHttpInputMessage(body, inputMessage.getHeaders());
53+
}
54+
55+
private String extractSchemaUri(MethodParameter parameter) {
56+
final JsonSchemaValidation annotation =
57+
parameter.getNestedParameterType().getAnnotation(JsonSchemaValidation.class);
58+
return annotation.value();
59+
}
60+
61+
@Override
62+
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
63+
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
64+
// Do not touch
65+
return body;
66+
}
67+
68+
@Override
69+
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter,
70+
Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
71+
// Do not touch
72+
return body;
73+
}
74+
75+
private String toString(HttpInputMessage inputMessage) throws IOException {
76+
final InputStream inputStream = inputMessage.getBody();
77+
return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
78+
}
79+
80+
private InputStream fromString(String body) {
81+
return new ByteArrayInputStream(body.getBytes());
82+
}
83+
84+
private HttpInputMessage buildHttpInputMessage(String body, HttpHeaders httpHeaders) {
85+
return new HttpInputMessage() {
86+
@Override
87+
public InputStream getBody() {
88+
return fromString(body);
89+
}
90+
91+
@Override
92+
public HttpHeaders getHeaders() {
93+
return httpHeaders;
94+
}
95+
};
96+
}
97+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.github.JanLoebel.jsonschemavalidation.provider;
2+
3+
import com.networknt.schema.JsonSchema;
4+
5+
import java.util.Map;
6+
import java.util.concurrent.ConcurrentHashMap;
7+
8+
public class CacheableJsonSchemaProvider extends DefaultJsonSchemaProvider {
9+
10+
private final Map<String, JsonSchema> cache = new ConcurrentHashMap<>();
11+
private final String basePath;
12+
13+
public CacheableJsonSchemaProvider(String basePath) {
14+
this.basePath = basePath;
15+
}
16+
17+
public CacheableJsonSchemaProvider() {
18+
this("");
19+
}
20+
21+
@Override
22+
public JsonSchema loadSchema(String url) {
23+
final String fullPath = basePath + url;
24+
if (cache.containsKey(fullPath)) {
25+
return cache.get(fullPath);
26+
}
27+
28+
return putToCache(fullPath, super.loadSchema(fullPath));
29+
}
30+
31+
private JsonSchema putToCache(String fullPath, JsonSchema jsonSchema) {
32+
cache.put(fullPath, jsonSchema);
33+
return jsonSchema;
34+
}
35+
36+
}

0 commit comments

Comments
 (0)