DATAMONGO-1322 - Polishing.

Remove ValidationAction/Level and ValidationOptions duplicates. Rename ValidatorDefinition to Validator and Validator to ValidationOptions.
Update and rename some of the tests. Also make sure to run the created validator document through the QueryMapper for value conversion and potential field mappings. Streamline Validator usage by providing plain Document and JsonSchema validator options next to the Criteria based one.
Finally update the reference documentation.

Original pull request: #525.
Related pull request: #511.
Related ticket: DATACMNS-1835.
This commit is contained in:
Christoph Strobl
2018-01-15 10:32:21 +01:00
committed by Mark Paluch
parent 2024b30059
commit 36614ef0ab
16 changed files with 504 additions and 333 deletions

View File

@@ -21,7 +21,7 @@ import java.util.Optional;
import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
import org.springframework.data.mongodb.core.validation.ValidatorDefinition;
import org.springframework.data.mongodb.core.validation.Validator;
import org.springframework.data.util.Optionals;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -43,7 +43,7 @@ public class CollectionOptions {
private @Nullable Long size;
private @Nullable Boolean capped;
private @Nullable Collation collation;
private Validator validator;
private ValidationOptions validationOptions;
/**
* Constructs a new <code>CollectionOptions</code> instance.
@@ -56,17 +56,17 @@ public class CollectionOptions {
*/
@Deprecated
public CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped) {
this(size, maxDocuments, capped, null, Validator.none());
this(size, maxDocuments, capped, null, ValidationOptions.none());
}
private CollectionOptions(@Nullable Long size, @Nullable Long maxDocuments, @Nullable Boolean capped,
@Nullable Collation collation, Validator validator) {
@Nullable Collation collation, ValidationOptions validationOptions) {
this.maxDocuments = maxDocuments;
this.size = size;
this.capped = capped;
this.collation = collation;
this.validator = validator;
this.validationOptions = validationOptions;
}
/**
@@ -80,7 +80,7 @@ public class CollectionOptions {
Assert.notNull(collation, "Collation must not be null!");
return new CollectionOptions(null, null, null, collation, Validator.none());
return new CollectionOptions(null, null, null, collation, ValidationOptions.none());
}
/**
@@ -90,7 +90,7 @@ public class CollectionOptions {
* @since 2.0
*/
public static CollectionOptions empty() {
return new CollectionOptions(null, null, null, null, Validator.none());
return new CollectionOptions(null, null, null, null, ValidationOptions.none());
}
/**
@@ -101,7 +101,7 @@ public class CollectionOptions {
* @since 2.0
*/
public CollectionOptions capped() {
return new CollectionOptions(size, maxDocuments, true, collation, validator);
return new CollectionOptions(size, maxDocuments, true, collation, validationOptions);
}
/**
@@ -112,7 +112,7 @@ public class CollectionOptions {
* @since 2.0
*/
public CollectionOptions maxDocuments(long maxDocuments) {
return new CollectionOptions(size, maxDocuments, capped, collation, validator);
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
}
/**
@@ -123,7 +123,7 @@ public class CollectionOptions {
* @since 2.0
*/
public CollectionOptions size(long size) {
return new CollectionOptions(size, maxDocuments, capped, collation, validator);
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
}
/**
@@ -134,11 +134,11 @@ public class CollectionOptions {
* @since 2.0
*/
public CollectionOptions collation(@Nullable Collation collation) {
return new CollectionOptions(size, maxDocuments, capped, collation, validator);
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
}
/**
* Create new {@link CollectionOptions} with already given settings and {@code validator} set to given
* Create new {@link CollectionOptions} with already given settings and {@code validationOptions} set to given
* {@link MongoJsonSchema}.
*
* @param schema can be {@literal null}.
@@ -146,11 +146,19 @@ public class CollectionOptions {
* @since 2.1
*/
public CollectionOptions schema(@Nullable MongoJsonSchema schema) {
return validation(new Validator(schema, null, validator.validationLevel, validator.validationAction));
return validator(Validator.schema(schema));
}
public CollectionOptions validatorDefinition(@Nullable ValidatorDefinition definition) {
return validation(new Validator(null, definition, validator.validationLevel, validator.validationAction));
/**
* /** Create new {@link CollectionOptions} with already given settings and {@code validationOptions} set to given
* {@link Validator}.
*
* @param validator can be {@literal null}.
* @return new {@link CollectionOptions}.
* @since 2.1
*/
public CollectionOptions validator(@Nullable Validator validator) {
return validation(validationOptions.validator(validator));
}
/**
@@ -219,7 +227,7 @@ public class CollectionOptions {
public CollectionOptions schemaValidationLevel(ValidationLevel validationLevel) {
Assert.notNull(validationLevel, "ValidationLevel must not be null!");
return validation(new Validator(validator.schema, validator.validatorDefinition, validationLevel, validator.validationAction));
return validation(validationOptions.validationLevel(validationLevel));
}
/**
@@ -233,20 +241,20 @@ public class CollectionOptions {
public CollectionOptions schemaValidationAction(ValidationAction validationAction) {
Assert.notNull(validationAction, "ValidationAction must not be null!");
return validation(new Validator(validator.schema, validator.validatorDefinition, validator.validationLevel, validationAction));
return validation(validationOptions.validationAction(validationAction));
}
/**
* Create new {@link CollectionOptions} with the given {@link Validator}.
* Create new {@link CollectionOptions} with the given {@link ValidationOptions}.
*
* @param validator must not be {@literal null}. Use {@link Validator#none()} to remove validation.
* @param validationOptions must not be {@literal null}. Use {@link ValidationOptions#none()} to remove validation.
* @return new {@link CollectionOptions}.
* @since 2.1
*/
public CollectionOptions validation(Validator validator) {
public CollectionOptions validation(ValidationOptions validationOptions) {
Assert.notNull(validator, "Validator must not be null!");
return new CollectionOptions(size, maxDocuments, capped, collation, validator);
Assert.notNull(validationOptions, "ValidationOptions must not be null!");
return new CollectionOptions(size, maxDocuments, capped, collation, validationOptions);
}
/**
@@ -293,47 +301,72 @@ public class CollectionOptions {
* @return {@link Optional#empty()} if not set.
* @since 2.1
*/
public Optional<Validator> getValidator() {
return validator.isEmpty() ? Optional.empty() : Optional.of(validator);
public Optional<ValidationOptions> getValidationOptions() {
return validationOptions.isEmpty() ? Optional.empty() : Optional.of(validationOptions);
}
/**
* Encapsulation of Validator options.
* Encapsulation of ValidationOptions options.
*
* @author Christoph Strobl
* @author Andreas Zink
* @since 2.1
*/
@RequiredArgsConstructor
public static class Validator {
public static class ValidationOptions {
private static final Validator NONE = new Validator(null, null, null, null);
private static final ValidationOptions NONE = new ValidationOptions(null, null, null);
private final @Nullable MongoJsonSchema schema;
private final @Nullable ValidatorDefinition validatorDefinition;
private final @Nullable Validator validator;
private final @Nullable ValidationLevel validationLevel;
private final @Nullable ValidationAction validationAction;
/**
* Create an empty {@link Validator}.
* Create an empty {@link ValidationOptions}.
*
* @return never {@literal null}.
*/
public static Validator none() {
public static ValidationOptions none() {
return NONE;
}
/**
* Get the {@code $jsonSchema} used for validation.
* Define the {@link Validator} to be used for document validation.
*
* @return {@link Optional#empty()} if not set.
* @param validator can be {@literal null}.
* @return new instance of {@link ValidationOptions}.
*/
public Optional<MongoJsonSchema> getSchema() {
return Optional.ofNullable(schema);
public ValidationOptions validator(@Nullable Validator validator) {
return new ValidationOptions(validator, validationLevel, validationAction);
}
public Optional<ValidatorDefinition> getValidatorDefinition() {
return Optional.ofNullable(validatorDefinition);
/**
* Define the validation level to apply.
*
* @param validationLevel can be {@literal null}.
* @return new instance of {@link ValidationOptions}.
*/
public ValidationOptions validationLevel(ValidationLevel validationLevel) {
return new ValidationOptions(validator, validationLevel, validationAction);
}
/**
* Define the validation action to take.
*
* @param validationAction can be {@literal null}.
* @return new instance of {@link ValidationOptions}.
*/
public ValidationOptions validationAction(ValidationAction validationAction) {
return new ValidationOptions(validator, validationLevel, validationAction);
}
/**
* Get the {@link Validator} to use.
*
* @return never {@literal null}.
*/
public Optional<Validator> getValidator() {
return Optional.ofNullable(validator);
}
/**
@@ -358,7 +391,7 @@ public class CollectionOptions {
* @return {@literal true} if no arguments set.
*/
boolean isEmpty() {
return !Optionals.isAnyPresent(getSchema(), getValidatorDefinition(), getValidationAction(), getValidationLevel());
return !Optionals.isAnyPresent(getValidator(), getValidationAction(), getValidationLevel());
}
}
}

View File

@@ -63,7 +63,7 @@ import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.BulkOperations.BulkMode;
import org.springframework.data.mongodb.core.CollectionOptions.Validator;
import org.springframework.data.mongodb.core.CollectionOptions.ValidationOptions;
import org.springframework.data.mongodb.core.DefaultBulkOperations.BulkOperationContext;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
@@ -108,6 +108,8 @@ import org.springframework.data.mongodb.core.query.Meta;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.validation.JsonSchemaValidator;
import org.springframework.data.mongodb.core.validation.Validator;
import org.springframework.data.mongodb.util.MongoClientVersion;
import org.springframework.data.projection.ProjectionInformation;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
@@ -139,10 +141,16 @@ import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.MongoIterable;
import com.mongodb.client.model.*;
import com.mongodb.client.model.CountOptions;
import com.mongodb.client.model.CreateCollectionOptions;
import com.mongodb.client.model.DeleteOptions;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.FindOneAndDeleteOptions;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.ReturnDocument;
import com.mongodb.client.model.UpdateOptions;
import com.mongodb.client.model.ValidationAction;
import com.mongodb.client.model.ValidationLevel;
import com.mongodb.client.model.ValidationOptions;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import com.mongodb.util.JSONParseException;
@@ -2236,7 +2244,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
if (collectionOptions.containsKey("validator")) {
ValidationOptions options = new ValidationOptions();
com.mongodb.client.model.ValidationOptions options = new com.mongodb.client.model.ValidationOptions();
if (collectionOptions.containsKey("validationLevel")) {
options.validationLevel(ValidationLevel.fromString(collectionOptions.getString("validationLevel")));
@@ -2377,10 +2385,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
Document doc = convertToDocument(collectionOptions);
if (collectionOptions != null && collectionOptions.getValidator().isPresent()) {
if (collectionOptions != null && collectionOptions.getValidationOptions().isPresent()) {
Validator v = collectionOptions.getValidator().get();
v.getSchema().ifPresent(val -> doc.put("validator", schemaMapper.mapSchema(val.toDocument(), targetType)));
ValidationOptions v = collectionOptions.getValidationOptions().get();
v.getValidator().ifPresent(val -> doc.put("validator", getMappedValidator(val, targetType)));
}
return doc;
}
@@ -2401,18 +2409,28 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware,
collectionOptions.getMaxDocuments().ifPresent(val -> document.put("max", val));
collectionOptions.getCollation().ifPresent(val -> document.append("collation", val.toDocument()));
if (collectionOptions.getValidator().isPresent()) {
Validator v = collectionOptions.getValidator().get();
if (collectionOptions.getValidationOptions().isPresent()) {
CollectionOptions.ValidationOptions v = collectionOptions.getValidationOptions().get();
v.getValidationLevel().ifPresent(val -> document.append("validationLevel", val.getValue()));
v.getValidationAction().ifPresent(val -> document.append("validationAction", val.getValue()));
v.getSchema().ifPresent(val -> document.append("validator",
new MongoJsonSchemaMapper(getConverter()).mapSchema(val.toDocument(), Object.class)));
v.getValidatorDefinition().ifPresent(val -> document.put("validator", val.toDocument()));
v.getValidator().ifPresent(val -> document.append("validator", getMappedValidator(val, Object.class)));
}
}
return document;
}
Document getMappedValidator(Validator validator, Class<?> domainType) {
Document validationRules = validator.toDocument();
if (validator instanceof JsonSchemaValidator || validationRules.containsKey("$jsonSchema")) {
return schemaMapper.mapSchema(validationRules, domainType);
}
return queryMapper.getMappedObject(validationRules, mappingContext.getPersistentEntity(domainType));
}
/**
* Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter.
* The first document that matches the query is returned and also removed from the collection in the database.

View File

@@ -24,8 +24,17 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -91,6 +100,8 @@ import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.NearQuery;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.core.validation.JsonSchemaValidator;
import org.springframework.data.mongodb.core.validation.Validator;
import org.springframework.data.mongodb.util.MongoClientVersion;
import org.springframework.data.projection.ProjectionInformation;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
@@ -2007,15 +2018,14 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
collectionOptions.getMaxDocuments().ifPresent(result::maxDocuments);
collectionOptions.getCollation().map(Collation::toMongoCollation).ifPresent(result::collation);
collectionOptions.getValidator().ifPresent(it -> {
collectionOptions.getValidationOptions().ifPresent(it -> {
ValidationOptions validationOptions = new ValidationOptions();
it.getValidationAction().ifPresent(validationOptions::validationAction);
it.getValidationLevel().ifPresent(validationOptions::validationLevel);
it.getSchema()
.ifPresent(val -> validationOptions.validator(schemaMapper.mapSchema(val.toDocument(), entityType)));
it.getValidator().ifPresent(val -> validationOptions.validator(getMappedValidator(val, entityType)));
result.validationOptions(validationOptions);
});
@@ -2023,6 +2033,17 @@ public class ReactiveMongoTemplate implements ReactiveMongoOperations, Applicati
return result;
}
Document getMappedValidator(Validator validator, Class<?> domainType) {
Document validationRules = validator.toDocument();
if (validator instanceof JsonSchemaValidator || validationRules.containsKey("$jsonSchema")) {
return schemaMapper.mapSchema(validationRules, domainType);
}
return queryMapper.getMappedObject(validationRules, mappingContext.getPersistentEntity(domainType));
}
/**
* Map the results of an ad-hoc query on the default MongoDB collection to an object using the template's converter.
* The first document that matches the query is returned and also removed from the collection in the database.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,51 +15,61 @@
*/
package org.springframework.data.mongodb.core.validation;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.lang.NonNull;
import org.springframework.data.mongodb.core.query.SerializationUtils;
import org.springframework.util.Assert;
/**
* Utility to build a MongoDB {@code validator} based on a {@link CriteriaDefinition}.
*
* {@link Validator} implementation based on {@link CriteriaDefinition query expressions}.
*
* @author Andreas Zink
* @author Christoph Strobl
* @since 2.1
* @see Criteria
* @see <a href="https://docs.mongodb.com/manual/core/schema-validation/#query-expressions">Schema Validation</a>
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode
public class CriteriaValidator implements ValidatorDefinition {
public class CriteriaValidator implements Validator {
private final Document document;
private CriteriaValidator(Document document) {
Assert.notNull(document, "Document must not be null!");
this.document = document;
}
private final CriteriaDefinition criteria;
/**
* Builds a {@code validator} object, which is basically setup of query operators, based on a
* Creates a new {@link Validator} object, which is basically setup of query operators, based on a
* {@link CriteriaDefinition} instance.
*
* @param criteria the criteria to build the {@code validator} from
* @return
*
* @param criteria the criteria to build the {@code validator} from. Must not be {@literal null}.
* @return new instance of {@link CriteriaValidator}.
* @throws IllegalArgumentException when criteria is {@literal null}.
*/
public static CriteriaValidator fromCriteria(@NonNull CriteriaDefinition criteria) {
public static CriteriaValidator of(CriteriaDefinition criteria) {
Assert.notNull(criteria, "Criteria must not be null!");
return new CriteriaValidator(criteria.getCriteriaObject());
return new CriteriaValidator(criteria);
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.validation.Validator#toDocument()
*/
@Override
public Document toDocument() {
return this.document;
return criteria.getCriteriaObject();
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return document.toString();
return SerializationUtils.serializeToJsonSafely(toDocument());
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2018 the original author or authors.
*
* 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 org.springframework.data.mongodb.core.validation;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.SerializationUtils;
import org.springframework.util.Assert;
/**
* Most trivial {@link Validator} implementation using plain {@link Document} to describe the desired document structure
* which can be either a {@code $jsonSchema} or query expression.
*
* @author Christoph Strobl
* @since 2.1
* @see <a href="https://docs.mongodb.com/manual/core/schema-validation/">Schema Validation</a>
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode
public class DocumentValidator implements Validator {
private final Document validatorObject;
/**
* Create new {@link org.springframework.data.mongodb.core.validation.DocumentValidator} defining validation rules via
* a plain {@link Document}.
*
* @param validatorObject must not be {@literal null}.
* @throws IllegalArgumentException if validatorObject is {@literal null}.
*/
public static DocumentValidator of(Document validatorObject) {
Assert.notNull(validatorObject, "ValidatorObject must not be null!");
return new DocumentValidator(new Document(validatorObject));
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.validation.Validator#toDocument()
*/
@Override
public Document toDocument() {
return new Document(validatorObject);
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return SerializationUtils.serializeToJsonSafely(validatorObject);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2018 the original author or authors.
*
* 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 org.springframework.data.mongodb.core.validation;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.RequiredArgsConstructor;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.SerializationUtils;
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
import org.springframework.util.Assert;
/**
* {@link Validator} implementation based on {@link MongoJsonSchema JSON Schema}.
*
* @author Christoph Strobl
* @since 2.1
* @see <a href="https://docs.mongodb.com/manual/core/schema-validation/#json-schema">Schema Validation</a>
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@EqualsAndHashCode
public class JsonSchemaValidator implements Validator {
private final MongoJsonSchema schema;
public static JsonSchemaValidator of(MongoJsonSchema schema) {
Assert.notNull(schema, "Schema must not be null!");
return new JsonSchemaValidator(schema);
}
public MongoJsonSchema getSchema() {
return schema;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mongodb.core.validation.Validator#toDocument()
*/
@Override
public Document toDocument() {
return schema.toDocument();
}
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return SerializationUtils.serializeToJsonSafely(toDocument());
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2017 the original author or authors.
*
* 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 org.springframework.data.mongodb.core.validation;
import lombok.Getter;
/**
* Determines whether to error on invalid documents or just warn about the violations but allow invalid documents to be
* inserted.
*
* @author Andreas Zink
* @since 2.1
* @see <a href="https://docs.mongodb.com/manual/reference/method/db.createCollection/">MongoDB Collection Options</a>
*/
public enum ValidationAction {
/**
* Documents must pass validation before the write occurs. Otherwise, the write operation fails. (MongoDB default)
*/
ERROR("error"),
/**
* Documents do not have to pass validation. If the document fails validation, the write operation logs the validation
* failure.
*/
WARN("warn");
@Getter private String value;
private ValidationAction(String value) {
this.value = value;
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright 2017 the original author or authors.
*
* 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 org.springframework.data.mongodb.core.validation;
import lombok.Getter;
/**
* Determines how strictly MongoDB applies the validation rules to existing documents during an update.
*
* @author Andreas Zink
* @since 2.1
* @see <a href="https://docs.mongodb.com/manual/reference/method/db.createCollection/">MongoDB Collection Options</a>
*/
public enum ValidationLevel {
/**
* No validation for inserts or updates.
*/
OFF("off"),
/**
* Apply validation rules to all inserts and all updates. (MongoDB default)
*/
STRICT("strict"),
/**
* Apply validation rules to inserts and to updates on existing valid documents. Do not apply rules to updates on
* existing invalid documents.
*/
MODERATE("moderate");
@Getter private String value;
private ValidationLevel(String value) {
this.value = value;
}
}

View File

@@ -1,74 +0,0 @@
/*
* Copyright 2017 the original author or authors.
*
* 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 org.springframework.data.mongodb.core.validation;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Optional;
import org.springframework.data.mongodb.core.CollectionOptions;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Wraps the collection validation options.
*
* @author Andreas Zink
* @since 2.1
* @see {@link CollectionOptions}
* @see <a href="https://docs.mongodb.com/manual/core/document-validation/">MongoDB Document Validation</a>
* @see <a href="https://docs.mongodb.com/manual/reference/method/db.createCollection/">MongoDB Collection Options</a>
*/
@EqualsAndHashCode
@ToString
public class ValidationOptions {
private ValidatorDefinition validator;
private ValidationLevel validationLevel;
private ValidationAction validationAction;
private ValidationOptions(ValidatorDefinition validator) {
Assert.notNull(validator, "ValidatorDefinition must not be null!");
this.validator = validator;
}
public static ValidationOptions validator(@NonNull ValidatorDefinition validator) {
return new ValidationOptions(validator);
}
public ValidationOptions validationLevel(@Nullable ValidationLevel validationLevel) {
this.validationLevel = validationLevel;
return this;
}
public ValidationOptions validationAction(@Nullable ValidationAction validationAction) {
this.validationAction = validationAction;
return this;
}
public ValidatorDefinition getValidator() {
return validator;
}
public Optional<ValidationLevel> getValidationLevel() {
return Optional.ofNullable(validationLevel);
}
public Optional<ValidationAction> getValidationAction() {
return Optional.ofNullable(validationAction);
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2018 the original author or authors.
*
* 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 org.springframework.data.mongodb.core.validation;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;
import org.springframework.data.mongodb.core.schema.MongoJsonSchema;
import org.springframework.util.Assert;
/**
* Provides a {@code validator} object to be used for collection validation via
* {@link org.springframework.data.mongodb.core.CollectionOptions.ValidationOptions}.
*
* @author Andreas Zink
* @author Christoph Strobl
* @since 2.1
* @see <a href="https://docs.mongodb.com/manual/reference/method/db.createCollection/">MongoDB Collection Options</a>
*/
public interface Validator {
/**
* Get the {@link Document} containing the validation specific rules. The document may contain fields that may require
* type and/or field name mapping.
*
* @return a MongoDB {@code validator} {@link Document}. Never {@literal null}.
*/
Document toDocument();
/**
* Creates a basic {@link Validator} checking documents against a given set of rules.
*
* @param validationRules must not be {@literal null}.
* @return new instance of {@link Validator}.
* @throws IllegalArgumentException if validationRules is {@literal null}.
*/
static Validator document(Document validationRules) {
Assert.notNull(validationRules, "ValidationRules must not be null!");
return DocumentValidator.of(validationRules);
}
/**
* Creates a new {@link Validator} checking documents against the structure defined in {@link MongoJsonSchema}.
*
* @param schema must not be {@literal null}.
* @return new instance of {@link Validator}.
* @throws IllegalArgumentException if schema is {@literal null}.
*/
static Validator schema(MongoJsonSchema schema) {
Assert.notNull(schema, "Schema must not be null!");
return JsonSchemaValidator.of(schema);
}
/**
* Creates a new {@link Validator} checking documents against a given query structure expressed by
* {@link CriteriaDefinition}. <br />
*
* @param criteria must not be {@literal null}.
* @return new instance of {@link Validator}.
* @throws IllegalArgumentException if criteria is {@literal null}.
*/
static Validator criteria(CriteriaDefinition criteria) {
Assert.notNull(criteria, "Criteria must not be null!");
return CriteriaValidator.of(criteria);
}
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright 2017 the original author or authors.
*
* 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 org.springframework.data.mongodb.core.validation;
import org.bson.Document;
import org.springframework.lang.NonNull;
/**
* Provides a {@code validator} object to be used for collection validation.
*
* @author Andreas Zink
* @since 2.1
* @see <a href="https://docs.mongodb.com/manual/reference/method/db.createCollection/">MongoDB Collection Options</a>
*/
public interface ValidatorDefinition {
/**
* @return a MongoDB {@code validator} document
*/
public @NonNull Document toDocument();
}

View File

@@ -0,0 +1,6 @@
/**
* MongoDB schema validation specifics.
*/
@org.springframework.lang.NonNullApi
@org.springframework.lang.NonNullFields
package org.springframework.data.mongodb.core.validation;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 the original author or authors.
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,46 +15,50 @@
*/
package org.springframework.data.mongodb.core;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.data.mongodb.core.query.Criteria.*;
import static org.springframework.data.mongodb.core.validation.Validator.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import org.bson.Document;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.core.CollectionOptions.ValidationOptions;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.validation.CriteriaValidator;
import org.springframework.data.mongodb.core.validation.ValidationAction;
import org.springframework.data.mongodb.core.validation.ValidationLevel;
import org.springframework.data.mongodb.core.validation.ValidationOptions;
import org.springframework.data.mongodb.core.validation.DocumentValidator;
import org.springframework.data.mongodb.test.util.MongoVersionRule;
import org.springframework.data.util.Version;
import org.springframework.lang.Nullable;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.mongodb.MongoClient;
import com.mongodb.client.model.ValidationAction;
import com.mongodb.client.model.ValidationLevel;
/**
* Integration tests for {@link CollectionOptions#validation(ValidationOptions)} using
* {@link org.springframework.data.mongodb.core.validation.CriteriaValidator} and {@link DocumentValidator}.
*
* @author Andreas Zink
* @author Christoph Strobl
*/
@RunWith(SpringJUnit4ClassRunner.class)
public class MongoTemplateValidationTests {
public static @ClassRule MongoVersionRule REQUIRES_AT_LEAST_3_2_0 = MongoVersionRule.atLeast(Version.parse("3.2.0"));
public static final String COLLECTION_NAME = "validation-1";
static final String COLLECTION_NAME = "validation-1";
@Configuration
static class Config extends AbstractMongoConfiguration {
@@ -79,73 +83,99 @@ public class MongoTemplateValidationTests {
@Test // DATAMONGO-1322
public void testCollectionWithSimpleCriteriaBasedValidation() {
Criteria criteria = Criteria.where("nonNullString").ne(null).type(2).and("rangedInteger").ne(null).type(16).gte(0)
.lte(122);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().validatorDefinition(CriteriaValidator.fromCriteria(criteria)));
Criteria criteria = where("nonNullString").ne(null).type(2).and("rangedInteger").ne(null).type(16).gte(0).lte(122);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().validator(criteria(criteria)));
Document validator = getValidatorInfo(COLLECTION_NAME);
assertThat(validator.get("nonNullString")).isEqualTo(new Document("$ne", null).append("$type", 2));
assertThat(validator.get("rangedInteger"))
.isEqualTo(new Document("$ne", null).append("$type", 16).append("$gte", 0).append("$lte", 122));
template.save(new SimpleBean("hello", 101), COLLECTION_NAME);
try {
template.save(new SimpleBean(null, 101), COLLECTION_NAME);
Assert.fail("The collection validation was setup to check for non-null string");
} catch (Exception e) {
// ignore
}
try {
template.save(new SimpleBean("hello", -1), COLLECTION_NAME);
Assert.fail("The collection validation was setup to check for non-negative int");
} catch (Exception e) {
// ignore
}
template.save(new SimpleBean("hello", 101, null), COLLECTION_NAME);
assertThatExceptionOfType(DataIntegrityViolationException.class)
.isThrownBy(() -> template.save(new SimpleBean(null, 101, null), COLLECTION_NAME));
assertThatExceptionOfType(DataIntegrityViolationException.class)
.isThrownBy(() -> template.save(new SimpleBean("hello", -1, null), COLLECTION_NAME));
}
@Test // DATAMONGO-1322
public void testCollectionValidationActionError() {
Criteria criteria = Criteria.where("name").type(2);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schemaValidationAction(com.mongodb.client.model.ValidationAction.ERROR).validatorDefinition(CriteriaValidator.fromCriteria(criteria)));
String validationAction = getValidationActionInfo(COLLECTION_NAME);
assertThat(ValidationAction.ERROR.getValue()).isEqualTo(validationAction);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schemaValidationAction(ValidationAction.ERROR)
.validator(criteria(where("name").type(2))));
assertThat(getValidationActionInfo(COLLECTION_NAME)).isEqualTo(ValidationAction.ERROR.getValue());
}
@Test // DATAMONGO-1322
public void testCollectionValidationActionWarn() {
Criteria criteria = Criteria.where("name").type(2);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schemaValidationAction(com.mongodb.client.model.ValidationAction.WARN).validatorDefinition(CriteriaValidator.fromCriteria(criteria)));
String validationAction = getValidationActionInfo(COLLECTION_NAME);
assertThat(ValidationAction.WARN.getValue()).isEqualTo(validationAction);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schemaValidationAction(ValidationAction.WARN)
.validator(criteria(where("name").type(2))));
assertThat(getValidationActionInfo(COLLECTION_NAME)).isEqualTo(ValidationAction.WARN.getValue());
}
@Test // DATAMONGO-1322
public void testCollectionValidationLevelOff() {
Criteria criteria = Criteria.where("name").type(2);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schemaValidationLevel(com.mongodb.client.model.ValidationLevel.OFF).validatorDefinition(CriteriaValidator.fromCriteria(criteria)));
String validationAction = getValidationLevelInfo(COLLECTION_NAME);
assertThat(ValidationLevel.OFF.getValue()).isEqualTo(validationAction);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schemaValidationLevel(ValidationLevel.OFF)
.validator(criteria(where("name").type(2))));
assertThat(getValidationLevelInfo(COLLECTION_NAME)).isEqualTo(ValidationLevel.OFF.getValue());
}
@Test // DATAMONGO-1322
public void testCollectionValidationLevelModerate() {
Criteria criteria = Criteria.where("name").type(2);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schemaValidationLevel(com.mongodb.client.model.ValidationLevel.MODERATE).validatorDefinition(CriteriaValidator.fromCriteria(criteria)));
String validationAction = getValidationLevelInfo(COLLECTION_NAME);
assertThat(ValidationLevel.MODERATE.getValue()).isEqualTo(validationAction);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schemaValidationLevel(ValidationLevel.MODERATE)
.validator(criteria(where("name").type(2))));
assertThat(getValidationLevelInfo(COLLECTION_NAME)).isEqualTo(ValidationLevel.MODERATE.getValue());
}
@Test // DATAMONGO-1322
public void testCollectionValidationLevelStrict() {
Criteria criteria = Criteria.where("name").type(2);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schemaValidationLevel(com.mongodb.client.model.ValidationLevel.STRICT).validatorDefinition(CriteriaValidator.fromCriteria(criteria)));
String validationAction = getValidationLevelInfo(COLLECTION_NAME);
assertThat(ValidationLevel.STRICT.getValue()).isEqualTo(validationAction);
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().schemaValidationLevel(ValidationLevel.STRICT)
.validator(criteria(where("name").type(2))));
assertThat(getValidationLevelInfo(COLLECTION_NAME)).isEqualTo(ValidationLevel.STRICT.getValue());
}
@Test // DATAMONGO-1322
public void mapsFieldNameCorrectlyWhenGivenDomainTypeInformation() {
template.createCollection(SimpleBean.class,
CollectionOptions.empty().validator(criteria(where("customFieldName").type(8))));
assertThat(getValidatorInfo(COLLECTION_NAME)).isEqualTo(new Document("customName", new Document("$type", 8)));
}
@Test // DATAMONGO-1322
public void usesDocumentValidatorCorrectly() {
Document rules = new Document("customFieldName", new Document("$type", "bool"));
template.createCollection(COLLECTION_NAME, CollectionOptions.empty().validator(document(rules)));
assertThat(getValidatorInfo(COLLECTION_NAME))
.isEqualTo(new Document("customFieldName", new Document("$type", "bool")));
}
@Test // DATAMONGO-1322
public void mapsDocumentValidatorFieldsCorrectly() {
Document rules = new Document("customFieldName", new Document("$type", "bool"));
template.createCollection(SimpleBean.class, CollectionOptions.empty().validator(document(rules)));
assertThat(getValidatorInfo(COLLECTION_NAME)).isEqualTo(new Document("customName", new Document("$type", "bool")));
}
private Document getCollectionOptions(String collectionName) {
@@ -165,6 +195,7 @@ public class MongoTemplateValidationTests {
}
private Document getCollectionInfo(String collectionName) {
return template.execute(db -> {
Document result = db.runCommand(
new Document().append("listCollections", 1).append("filter", new Document("name", collectionName)));
@@ -174,10 +205,12 @@ public class MongoTemplateValidationTests {
@Data
@AllArgsConstructor
@NoArgsConstructor
@org.springframework.data.mongodb.core.mapping.Document(collection = COLLECTION_NAME)
static class SimpleBean {
@NotNull private String nonNullString;
@NotNull @Min(0) @Max(122) private Integer rangedInteger;
private @Nullable String nonNullString;
private @Nullable Integer rangedInteger;
private @Field("customName") Object customFieldName;
}
}

View File

@@ -19,6 +19,7 @@ import static org.springframework.data.mongodb.test.util.Assertions.*;
import lombok.Data;
import java.util.Collections;
import java.util.List;
import org.bson.Document;
@@ -33,6 +34,7 @@ import org.springframework.data.mongodb.core.CollectionOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoJsonSchemaMapper;
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.validation.Validator;
import org.springframework.data.mongodb.test.util.MongoVersionRule;
import org.springframework.data.util.Version;
import org.springframework.test.context.ContextConfiguration;
@@ -99,6 +101,20 @@ public class MongoJsonSchemaTests {
assertThat(fromDb).isEqualTo($jsonSchema);
}
@Test // DATAMONGO-1835
public void writeSchemaInDocumentValidatorCorrectly() {
Document unmappedSchema = new Document("$jsonSchema",
new Document("type", "object").append("required", Collections.singletonList("firstname")));
Document mappedSchema = new Document("$jsonSchema",
new Document("type", "object").append("required", Collections.singletonList("first_name")));
template.createCollection(Person.class, CollectionOptions.empty().validator(Validator.document(unmappedSchema)));
assertThat(readSchemaFromDatabase("persons")).isEqualTo(mappedSchema);
}
@Test // DATAMONGO-1835
public void nonMappedSchema() {

View File

@@ -22,14 +22,18 @@ import org.junit.Test;
import org.springframework.data.mongodb.core.query.Criteria;
/**
* Unit tests for {@link CriteriaValidator}.
*
* @author Andreas Zink
* @author Christoph Strobl
*/
public class CriteriaValidatorTest {
public class CriteriaValidatorUnitTests {
@Test // DATAMONGO-1322
public void testSimpleCriteria() {
Criteria criteria = Criteria.where("nonNullString").ne(null).type(2).and("rangedInteger").type(16).gte(0).lte(122);
Document validator = CriteriaValidator.fromCriteria(criteria).toDocument();
Document validator = CriteriaValidator.of(criteria).toDocument();
assertThat(validator.get("nonNullString")).isEqualTo(new Document("$ne", null).append("$type", 2));
assertThat(validator.get("rangedInteger"))
@@ -38,6 +42,6 @@ public class CriteriaValidatorTest {
@Test(expected = IllegalArgumentException.class) // DATAMONGO-1322
public void testFailOnNull() {
CriteriaValidator.fromCriteria(null);
CriteriaValidator.of(null);
}
}

View File

@@ -96,6 +96,23 @@ MongoDB 3.6 allows validation and querying of documents using JSON schema draft
Spring Data MongoDB supports MongoDB's specific JSON schema implementation to define and use schemas. See <<mongo.jsonSchema,JSON Schema>> for further details.
[[mongo.mongo-3.validation.query-expression]]
==== Query Expression Validation
Next to the <<mongo.mongo-3.validation.json-schema>> as of version 3.2 MongoDB supports validating documents against a given query structure. The structure can be built using `Criteria` objects just the same way as they are used for defining queries.
[source,java]
----
Criteria queryExpression = Criteria.where("lastname").ne(null).type(2)
.and("age").ne(null).type(16).gt(0).lte(150);
Validator validator = Validator.criteria(queryExpression);
template.createCollection(Person.class, CollectionOptions.empty().validator(validator));
----
NOTE: Field names used within the query expression are mapped to the domain types property names taking potential `@Field` annotations into account.
[[mongo.mongo-3.misc]]
=== Other things to be aware of