Skip to content

Commit 14ccb51

Browse files
christophstroblmp911de
authored andcommitted
DATAMONGO-1835 - Add support for JSON Schema.
We now can create a $jsonSchema that can be used as a validator when creating collections and as predicate for queries. Required fields and properties get mapped according to the @field annotation on domain objects. MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname") .properties(string("firstname").possibleValues("luke", "han"), object("address").properties(string("postCode").minLength(4).maxLength(5))) .build(); resulting in the following schema: { "type": "object", "required": [ "firstname", "lastname" ], "properties": { "firstname": { "type": "string", "enum": [ "luke", "han" ], }, "address": { "type": "object", "properties": { "postCode": { "type": "string", "minLength": 4, "maxLength": 5 } } } } } Query usage: MongoJsonSchema schema = MongoJsonSchema.builder() .required("address") .property(object("address").properties(string("street").matching("^Apple.*"))).build(); List<Person> person = template.find(query(matchingDocumentStructure(schema)), Person.class)); Collection validation: MongoJsonSchema schema = MongoJsonSchema.builder().required("firstname", "lastname") .properties(string("firstname").possibleValues("luke", "han"), object("address").properties(string("postCode").minLength(4).maxLength(5))) .build(); template.createCollection(Person.class, CollectionOptions.empty() .schema(schema) .failOnValidationError()); Original pull request: #524.
1 parent ddc6e4a commit 14ccb51

21 files changed

+4840
-17
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/CollectionOptions.java

Lines changed: 198 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@
1818
import java.util.Optional;
1919

2020
import org.springframework.data.mongodb.core.query.Collation;
21+
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
22+
import org.springframework.data.util.Optionals;
2123
import org.springframework.lang.Nullable;
2224
import org.springframework.util.Assert;
2325

26+
import com.mongodb.client.model.ValidationAction;
27+
import com.mongodb.client.model.ValidationLevel;
28+
2429
/**
2530
* Provides a simple wrapper to encapsulate the variety of settings you can use when creating a collection.
2631
*
@@ -34,6 +39,7 @@ public class CollectionOptions {
3439
private @Nullable Long size;
3540
private @Nullable Boolean capped;
3641
private @Nullable Collation collation;
42+
private Validator validator;
3743

3844
/**
3945
* Constructs a new <code>CollectionOptions</code> instance.
@@ -46,16 +52,17 @@ public class CollectionOptions {
4652
*/
4753
@Deprecated
4854
public CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped) {
49-
this(size, maxDocuments, capped, null);
55+
this(size, maxDocuments, capped, null, Validator.none());
5056
}
5157

5258
private CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped,
53-
@Nullable Collation collation) {
59+
@Nullable Collation collation, Validator validator) {
5460

5561
this.maxDocuments = maxDocuments;
5662
this.size = size;
5763
this.capped = capped;
5864
this.collation = collation;
65+
this.validator = validator;
5966
}
6067

6168
/**
@@ -69,7 +76,7 @@ public static CollectionOptions just(Collation collation) {
6976

7077
Assert.notNull(collation, "Collation must not be null!");
7178

72-
return new CollectionOptions(null, null, null, collation);
79+
return new CollectionOptions(null, null, null, collation, Validator.none());
7380
}
7481

7582
/**
@@ -79,7 +86,7 @@ public static CollectionOptions just(Collation collation) {
7986
* @since 2.0
8087
*/
8188
public static CollectionOptions empty() {
82-
return new CollectionOptions(null, null, null, null);
89+
return new CollectionOptions(null, null, null, null, Validator.none());
8390
}
8491

8592
/**
@@ -90,7 +97,7 @@ public static CollectionOptions empty() {
9097
* @since 2.0
9198
*/
9299
public CollectionOptions capped() {
93-
return new CollectionOptions(size, maxDocuments, true, collation);
100+
return new CollectionOptions(size, maxDocuments, true, collation, validator);
94101
}
95102

96103
/**
@@ -101,7 +108,7 @@ public CollectionOptions capped() {
101108
* @since 2.0
102109
*/
103110
public CollectionOptions maxDocuments(long maxDocuments) {
104-
return new CollectionOptions(size, maxDocuments, capped, collation);
111+
return new CollectionOptions(size, maxDocuments, capped, collation, validator);
105112
}
106113

107114
/**
@@ -112,7 +119,7 @@ public CollectionOptions maxDocuments(long maxDocuments) {
112119
* @since 2.0
113120
*/
114121
public CollectionOptions size(long size) {
115-
return new CollectionOptions(size, maxDocuments, capped, collation);
122+
return new CollectionOptions(size, maxDocuments, capped, collation, validator);
116123
}
117124

118125
/**
@@ -123,7 +130,111 @@ public CollectionOptions size(long size) {
123130
* @since 2.0
124131
*/
125132
public CollectionOptions collation(@Nullable Collation collation) {
126-
return new CollectionOptions(size, maxDocuments, capped, collation);
133+
return new CollectionOptions(size, maxDocuments, capped, collation, validator);
134+
}
135+
136+
/**
137+
* Create new {@link CollectionOptions} with already given settings and {@code validator} set to given
138+
* {@link MongoJsonSchema}.
139+
*
140+
* @param schema can be {@literal null}.
141+
* @return new {@link CollectionOptions}.
142+
* @since 2.1
143+
*/
144+
public CollectionOptions schema(@Nullable MongoJsonSchema schema) {
145+
return validation(new Validator(schema, validator.validationLevel, validator.validationAction));
146+
}
147+
148+
/**
149+
* Create new {@link CollectionOptions} with already given settings and {@code validationLevel} set to {@code off}.
150+
*
151+
* @return new {@link CollectionOptions}.
152+
* @since 2.1
153+
*/
154+
public CollectionOptions disableValidation() {
155+
return schemaValidationLevel(ValidationLevel.OFF);
156+
}
157+
158+
/**
159+
* Create new {@link CollectionOptions} with already given settings and {@code validationLevel} set to {@code strict}.
160+
*
161+
* @return new {@link CollectionOptions}.
162+
* @since 2.1
163+
*/
164+
public CollectionOptions strictValidation() {
165+
return schemaValidationLevel(ValidationLevel.STRICT);
166+
}
167+
168+
/**
169+
* Create new {@link CollectionOptions} with already given settings and {@code validationLevel} set to
170+
* {@code moderate}.
171+
*
172+
* @return new {@link CollectionOptions}.
173+
* @since 2.1
174+
*/
175+
public CollectionOptions moderateValidation() {
176+
return schemaValidationLevel(ValidationLevel.MODERATE);
177+
}
178+
179+
/**
180+
* Create new {@link CollectionOptions} with already given settings and {@code validationAction} set to {@code warn}.
181+
*
182+
* @return new {@link CollectionOptions}.
183+
* @since 2.1
184+
*/
185+
public CollectionOptions warnOnValidationError() {
186+
return schemaValidationAction(ValidationAction.WARN);
187+
}
188+
189+
/**
190+
* Create new {@link CollectionOptions} with already given settings and {@code validationAction} set to {@code error}.
191+
*
192+
* @return new {@link CollectionOptions}.
193+
* @since 2.1
194+
*/
195+
public CollectionOptions failOnValidationError() {
196+
return schemaValidationAction(ValidationAction.ERROR);
197+
}
198+
199+
/**
200+
* Create new {@link CollectionOptions} with already given settings and {@code validationLevel} set given
201+
* {@link ValidationLevel}.
202+
*
203+
* @param validationLevel must not be {@literal null}.
204+
* @return new {@link CollectionOptions}.
205+
* @since 2.1
206+
*/
207+
public CollectionOptions schemaValidationLevel(ValidationLevel validationLevel) {
208+
209+
Assert.notNull(validationLevel, "ValidationLevel must not be null!");
210+
return validation(new Validator(validator.schema, validationLevel, validator.validationAction));
211+
}
212+
213+
/**
214+
* Create new {@link CollectionOptions} with already given settings and {@code validationAction} set given
215+
* {@link ValidationAction}.
216+
*
217+
* @param validationAction must not be {@literal null}.
218+
* @return new {@link CollectionOptions}.
219+
* @since 2.1
220+
*/
221+
public CollectionOptions schemaValidationAction(ValidationAction validationAction) {
222+
223+
Assert.notNull(validationAction, "ValidationAction must not be null!");
224+
return validation(new Validator(validator.schema, validator.validationLevel, validationAction));
225+
}
226+
227+
/**
228+
* Create new {@link CollectionOptions} with the given {@link Validator}.
229+
*
230+
* @param validator must not be {@literal null}. Use {@link Validator#none()} to remove validation.
231+
* @return new {@link CollectionOptions}.
232+
* @since 2.1
233+
*/
234+
public CollectionOptions validation(Validator validator) {
235+
236+
Assert.notNull(validator, "Validator must not be null!");
237+
return new CollectionOptions(size, maxDocuments, capped, collation, validator);
127238
}
128239

129240
/**
@@ -163,4 +274,83 @@ public Optional<Boolean> getCapped() {
163274
public Optional<Collation> getCollation() {
164275
return Optional.ofNullable(collation);
165276
}
277+
278+
/**
279+
* Get the {@link MongoJsonSchema} for the collection.
280+
*
281+
* @return {@link Optional#empty()} if not set.
282+
* @since 2.1
283+
*/
284+
public Optional<Validator> getValidator() {
285+
return validator.isEmpty() ? Optional.empty() : Optional.of(validator);
286+
}
287+
288+
/**
289+
* Encapsulation of Validator options.
290+
*
291+
* @author Christoph Strobl
292+
* @since 2.1
293+
*/
294+
public static class Validator {
295+
296+
private static final Validator NONE = new Validator(null, null, null);
297+
298+
private @Nullable MongoJsonSchema schema;
299+
private @Nullable ValidationLevel validationLevel;
300+
private @Nullable ValidationAction validationAction;
301+
302+
private Validator(@Nullable MongoJsonSchema schema, @Nullable ValidationLevel validationLevel,
303+
@Nullable ValidationAction validationAction) {
304+
305+
this.schema = schema;
306+
this.validationLevel = validationLevel;
307+
this.validationAction = validationAction;
308+
}
309+
310+
/**
311+
* Create an empty {@link Validator}.
312+
*
313+
* @return never {@literal null}.
314+
*/
315+
public static Validator none() {
316+
return NONE;
317+
}
318+
319+
/**
320+
* Get the {@code $jsonSchema} used for validation.
321+
*
322+
* @return {@link Optional#empty()} if not set.
323+
*/
324+
@Nullable
325+
public Optional<MongoJsonSchema> getSchema() {
326+
return Optional.ofNullable(schema);
327+
}
328+
329+
/**
330+
* Get the {@code validationLevel} to apply.
331+
*
332+
* @return {@link Optional#empty()} if not set.
333+
*/
334+
@Nullable
335+
public Optional<ValidationLevel> getValidationLevel() {
336+
return Optional.ofNullable(validationLevel);
337+
}
338+
339+
/**
340+
* Get the {@code validationAction} to perform.
341+
*
342+
* @return @return {@link Optional#empty()} if not set.
343+
*/
344+
@Nullable
345+
public Optional<ValidationAction> getValidationAction() {
346+
return Optional.ofNullable(validationAction);
347+
}
348+
349+
/**
350+
* @return {@literal true} if no arguments set.
351+
*/
352+
boolean isEmpty() {
353+
return !Optionals.isAnyPresent(getSchema(), getValidationAction(), getValidationLevel());
354+
}
355+
}
166356
}

0 commit comments

Comments
 (0)