diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java index 8b6bb03875..19d14bdb44 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/StringOperators.java @@ -686,10 +686,28 @@ public RegexMatch regexMatch(String regex, String options) { private RegexMatch createRegexMatch() { return usesFieldRef() ? RegexMatch.valueOf(fieldReference) : RegexMatch.valueOf(expression); } + + public ReplaceOne replaceOne(String find,String replacement) { + return createReplaceOne().find(find).replacement(replacement); + } + + private ReplaceOne createReplaceOne() { + return usesFieldRef() ? ReplaceOne.valueOf(fieldReference) : ReplaceOne.valueOf(expression); + } + + public ReplaceAll replaceAll(String find,String replacement) { + return createReplaceAll().find(find).replacement(replacement); + } + + private ReplaceAll createReplaceAll() { + return usesFieldRef() ? ReplaceAll.valueOf(fieldReference) : ReplaceAll.valueOf(expression); + } private boolean usesFieldRef() { return fieldReference != null; } + + } /** @@ -2078,4 +2096,260 @@ protected String getMongoMethod() { return "$regexMatch"; } } + + /** + * {@link AggregationExpression} for {@code $replaceOne} which replaces the first instance of a search string in an + * input string with a replacement string.
+ * NOTE: Requires MongoDB 4.4 or later. + */ + public static class ReplaceOne extends AbstractAggregationExpression { + + protected ReplaceOne(Object value) { + super(value); + } + + /** + * Creates new {@link ReplaceOne} using the value of the provided {@link Field fieldReference} as {@literal input} + * value. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link ReplaceOne}. + */ + public static ReplaceOne valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + + return new ReplaceOne(Collections.singletonMap("input", Fields.field(fieldReference))); + } + + /** + * Creates new {@link ReplaceOne} using the result of the provided {@link AggregationExpression} as {@literal input} + * value. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link ReplaceOne}. + */ + public static ReplaceOne valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + + return new ReplaceOne(Collections.singletonMap("input", expression)); + } + + /** + * The string to use to replace the first matched instance of {@code find} in input. + * + * @param replacement must not be {@literal null}. + * @return new instance of {@link ReplaceOne}. + */ + public ReplaceOne replacement(String replacement) { + + Assert.notNull(replacement, "Replacement must not be null!"); + + return new ReplaceOne(append("replacement", replacement)); + } + + /** + * Specifies the reference to the {@link Field field} holding the string to use to replace the first matched + * instance of {@code find} in input. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link ReplaceOne}. + */ + public ReplaceOne replacementOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + + return new ReplaceOne(append("replacement", Fields.field(fieldReference))); + } + + /** + * Specifies the {@link AggregationExpression} evaluating to the string to use to replace the first matched instance + * of {@code find} in {@code input}. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link ReplaceOne}. + */ + public ReplaceOne replacementOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + + return new ReplaceOne(append("replacement", expression)); + } + + /** + * The string to search for within the given input field. + * + * @param find must not be {@literal null}. + * @return new instance of {@link ReplaceOne}. + */ + public ReplaceOne find(String searchStr) { + + Assert.notNull(searchStr, "Search string must not be null!"); + + Map search = append("find", searchStr); + + return new ReplaceOne(search); + } + + /** + * Specify the reference to the {@link Field field} holding the string to search for within the given input field. + * + * @param find must not be {@literal null}. + * @return new instance of {@link ReplaceOne}. + */ + public ReplaceOne findOf(String fieldReference) { + + Assert.notNull(fieldReference, "fieldReference must not be null!"); + + return new ReplaceOne(append("find", fieldReference)); + } + + /** + * Specify the {@link AggregationExpression} evaluating to the the string to search for within the given input + * field. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link ReplaceOne}. + */ + public ReplaceOne findOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + + return new ReplaceOne(append("find", expression)); + } + + @Override + protected String getMongoMethod() { + return "$replaceOne"; + } + } + + /** + * {@link AggregationExpression} for {@code $replaceAll} which replaces all instances of a search string in an input + * string with a replacement string.
+ * NOTE: Requires MongoDB 4.4 or later. + */ + public static class ReplaceAll extends AbstractAggregationExpression { + + protected ReplaceAll(Object value) { + super(value); + } + + /** + * Creates new {@link ReplaceAll} using the value of the provided {@link Field fieldReference} as {@literal input} + * value. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link ReplaceAll}. + */ + public static ReplaceAll valueOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + + return new ReplaceAll(Collections.singletonMap("input", Fields.field(fieldReference))); + } + + /** + * Creates new {@link ReplaceAll} using the result of the provided {@link AggregationExpression} as {@literal input} + * value. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link ReplaceAll}. + */ + public static ReplaceAll valueOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + + return new ReplaceAll(Collections.singletonMap("input", expression)); + } + + /** + * The string to use to replace the first matched instance of {@code find} in input. + * + * @param replacement must not be {@literal null}. + * @return new instance of {@link ReplaceAll}. + */ + public ReplaceAll replacement(String replacement) { + + Assert.notNull(replacement, "Replacement must not be null!"); + + return new ReplaceAll(append("replacement", replacement)); + } + + /** + * Specifies the reference to the {@link Field field} holding the string to use to replace the first matched + * instance of {@code find} in input. + * + * @param fieldReference must not be {@literal null}. + * @return new instance of {@link ReplaceAll}. + */ + public ReplaceAll replacementOf(String fieldReference) { + + Assert.notNull(fieldReference, "FieldReference must not be null!"); + + return new ReplaceAll(append("replacement", Fields.field(fieldReference))); + } + + /** + * Specifies the {@link AggregationExpression} evaluating to the string to use to replace the first matched instance + * of {@code find} in input. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link ReplaceAll}. + */ + public ReplaceAll replacementOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + + return new ReplaceAll(append("replacement", expression)); + } + + /** + * The string to search for within the given input field. + * + * @param find must not be {@literal null}. + * @return new instance of {@link ReplaceAll}. + */ + public ReplaceAll find(String searchStr) { + + Assert.notNull(searchStr, "Search string must not be null!"); + + Map search = append("find", searchStr); + + return new ReplaceAll(search); + } + + /** + * Specify the reference to the {@link Field field} holding the string to search for within the given input field. + * + * @param find must not be {@literal null}. + * @return new instance of {@link ReplaceAll}. + */ + public ReplaceAll findOf(String fieldReference) { + + Assert.notNull(fieldReference, "fieldReference must not be null!"); + + return new ReplaceAll(append("find", fieldReference)); + } + + /** + * Specify the {@link AggregationExpression} evaluating to the string to search for within the given input field. + * + * @param expression must not be {@literal null}. + * @return new instance of {@link ReplaceAll}. + */ + public ReplaceAll findOf(AggregationExpression expression) { + + Assert.notNull(expression, "Expression must not be null!"); + + return new ReplaceAll(append("find", expression)); + } + + @Override + protected String getMongoMethod() { + return "$replaceAll"; + } + } + } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java index dc7a3cc982..5d9e839ae0 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/spel/MethodReferenceNode.java @@ -124,6 +124,8 @@ public class MethodReferenceNode extends ExpressionNode { map.put("regexFind", mapArgRef().forOperator("$regexFind").mappingParametersTo("input", "regex" , "options")); map.put("regexFindAll", mapArgRef().forOperator("$regexFindAll").mappingParametersTo("input", "regex" , "options")); map.put("regexMatch", mapArgRef().forOperator("$regexMatch").mappingParametersTo("input", "regex" , "options")); + map.put("replaceOne", mapArgRef().forOperator("$replaceOne").mappingParametersTo("input", "find" , "replacement")); + map.put("replaceAll", mapArgRef().forOperator("$replaceAll").mappingParametersTo("input", "find" , "replacement")); // TEXT SEARCH OPERATORS map.put("meta", singleArgRef().forOperator("$meta")); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java index 899e02a172..fd4aa70988 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/SpelExpressionTransformerUnitTests.java @@ -864,6 +864,20 @@ void shouldRenderRegexMatchWithOptionsFromFieldReference() { assertThat(transform("regexMatch(field1,'e',field2)")) .isEqualTo("{ \"$regexMatch\" : {\"input\" : \"$field1\" , \"regex\" : \"e\" , \"options\" : \"$field2\"}}"); } + + @Test + void shouldRenderReplaceOne() { + + assertThat(transform("replaceOne(field,'bar','baz')")) + .isEqualTo("{ \"$replaceOne\" : {\"input\" : \"$field\" , \"find\" : \"bar\" , \"replacement\" : \"baz\"}}"); + } + + @Test + void shouldRenderReplaceAll() { + + assertThat(transform("replaceAll(field,'bar','baz')")) + .isEqualTo("{ \"$replaceAll\" : {\"input\" : \"$field\" , \"find\" : \"bar\" , \"replacement\" : \"baz\"}}"); + } @Test // DATAMONGO-2077 diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/StringOperatorsUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/StringOperatorsUnitTests.java index d8ba5129e0..35f9611fc5 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/StringOperatorsUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/StringOperatorsUnitTests.java @@ -281,4 +281,35 @@ void shouldRenderRegexFindWithOptionsExpression() { .isEqualTo("{ $regexFind: { \"input\" : \"$shrewd\", \"regex\" : \"e\" , \"options\" : " + EXPRESSION_STRING + " } } "); } + + @Test + void shouldRenderReplaceOne() { + + assertThat(StringOperators.valueOf("bar").replaceOne("foobar","baz").toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo("{ $replaceOne : {\"find\" : \"foobar\", \"input\" : \"$bar\", \"replacement\" : \"baz\"}}"); + } + + @Test + void shouldRenderReplaceOneForExpression() { + + assertThat(StringOperators.valueOf(EXPRESSION).replaceOne("a","s").toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo("{ $replaceOne : {\"find\" : \"a\", \"input\" : " + EXPRESSION_STRING + ", \"replacement\" : \"s\"}}"); + } + + @Test + void shouldRenderReplaceAll() { + + assertThat(StringOperators.valueOf("bar").replaceAll("foobar","baz").toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo("{ $replaceAll : {\"find\" : \"foobar\", \"input\" : \"$bar\", \"replacement\" : \"baz\"}}"); + } + + @Test + void shouldRenderReplaceAllForExpression() { + + assertThat(StringOperators.valueOf(EXPRESSION).replaceAll("a","s").toDocument(Aggregation.DEFAULT_CONTEXT)) + .isEqualTo("{ $replaceAll : {\"find\" : \"a\", \"input\" : " + EXPRESSION_STRING + ", \"replacement\" : \"s\"}}"); + } + + + } diff --git a/spring-data-mongodb/src/test/resources/reactive-infrastructure.xml b/spring-data-mongodb/src/test/resources/reactive-infrastructure.xml index 896bb26812..1abf2324bf 100644 --- a/spring-data-mongodb/src/test/resources/reactive-infrastructure.xml +++ b/spring-data-mongodb/src/test/resources/reactive-infrastructure.xml @@ -5,7 +5,7 @@ - +