Skip to content

DATAMONGO-2112 - Allow usage of SpEL expression for index annotation attributes. #647

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version>
<version>2.2.0.DATAMONGO-2112-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-benchmarks/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version>
<version>2.2.0.DATAMONGO-2112-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version>
<version>2.2.0.DATAMONGO-2112-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>2.2.0.BUILD-SNAPSHOT</version>
<version>2.2.0.DATAMONGO-2112-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,29 @@
public @interface CompoundIndex {

/**
* The actual index definition in JSON format. The keys of the JSON document are the fields to be indexed, the values
* define the index direction (1 for ascending, -1 for descending). <br />
* The actual index definition in JSON format or a {@link org.springframework.expression.spel.standard.SpelExpression
* template expression} resolving to either a JSON String or a {@link org.bson.Document}. The keys of the JSON
* document are the fields to be indexed, the values define the index direction (1 for ascending, -1 for descending).
* <br />
* If left empty on nested document, the whole document will be indexed.
*
* <pre>
* <code>
*
* &#64;Document
* &#64;CompoundIndex(def = "{'h1': 1, 'h2': 1}")
* class JsonStringIndexDefinition {
* String h1, h2;
* }
*
* &#64;Document
* &#64;CompoundIndex(def = "#{T(org.bson.Document).parse("{ 'h1': 1, 'h2': 1 }")}")
* class ExpressionIndexDefinition {
* String h1, h2;
* }
* </code>
* </pre>
*
* @return
*/
String def() default "";
Expand Down Expand Up @@ -79,7 +98,8 @@
boolean dropDups() default false;

/**
* The name of the index to be created. <br />
* Index name of the index to be created either as plain value or as
* {@link org.springframework.expression.spel.standard.SpelExpression template expression}. <br />
* <br />
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
* provided name will be prefixed with the path leading to the entity. <br />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* Copyright 2012-2019 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
*
* https://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.index;

import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
* Duration format styles.
* <p/>
* Fork of {@code org.springframework.boot.convert.DurationStyle}.
*
* @author Phillip Webb
* @since 2.2
*/
enum DurationStyle {

/**
* Simple formatting, for example '1s'.
*/
SIMPLE("^([\\+\\-]?\\d+)([a-zA-Z]{0,2})$") {

@Override
public Duration parse(String value, @Nullable ChronoUnit unit) {
try {
Matcher matcher = matcher(value);
Assert.state(matcher.matches(), "Does not match simple duration pattern");
String suffix = matcher.group(2);
return (StringUtils.hasLength(suffix) ? Unit.fromSuffix(suffix) : Unit.fromChronoUnit(unit))
.parse(matcher.group(1));
} catch (Exception ex) {
throw new IllegalArgumentException("'" + value + "' is not a valid simple duration", ex);
}
}
},

/**
* ISO-8601 formatting.
*/
ISO8601("^[\\+\\-]?P.*$") {

@Override
public Duration parse(String value, @Nullable ChronoUnit unit) {
try {
return Duration.parse(value);
} catch (Exception ex) {
throw new IllegalArgumentException("'" + value + "' is not a valid ISO-8601 duration", ex);
}
}
};

private final Pattern pattern;

DurationStyle(String pattern) {
this.pattern = Pattern.compile(pattern);
}

protected final boolean matches(String value) {
return this.pattern.matcher(value).matches();
}

protected final Matcher matcher(String value) {
return this.pattern.matcher(value);
}

/**
* Parse the given value to a duration.
*
* @param value the value to parse
* @return a duration
*/
public Duration parse(String value) {
return parse(value, null);
}

/**
* Parse the given value to a duration.
*
* @param value the value to parse
* @param unit the duration unit to use if the value doesn't specify one ({@code null} will default to ms)
* @return a duration
*/
public abstract Duration parse(String value, @Nullable ChronoUnit unit);

/**
* Detect the style then parse the value to return a duration.
*
* @param value the value to parse
* @return the parsed duration
* @throws IllegalStateException if the value is not a known style or cannot be parsed
*/
public static Duration detectAndParse(String value) {
return detectAndParse(value, null);
}

/**
* Detect the style then parse the value to return a duration.
*
* @param value the value to parse
* @param unit the duration unit to use if the value doesn't specify one ({@code null} will default to ms)
* @return the parsed duration
* @throws IllegalStateException if the value is not a known style or cannot be parsed
*/
public static Duration detectAndParse(String value, @Nullable ChronoUnit unit) {
return detect(value).parse(value, unit);
}

/**
* Detect the style from the given source value.
*
* @param value the source value
* @return the duration style
* @throws IllegalStateException if the value is not a known style
*/
public static DurationStyle detect(String value) {
Assert.notNull(value, "Value must not be null");
for (DurationStyle candidate : values()) {
if (candidate.matches(value)) {
return candidate;
}
}
throw new IllegalArgumentException("'" + value + "' is not a valid duration");
}

/**
* Units that we support.
*/
enum Unit {

/**
* Milliseconds.
*/
MILLIS(ChronoUnit.MILLIS, "ms", Duration::toMillis),

/**
* Seconds.
*/
SECONDS(ChronoUnit.SECONDS, "s", Duration::getSeconds),

/**
* Minutes.
*/
MINUTES(ChronoUnit.MINUTES, "m", Duration::toMinutes),

/**
* Hours.
*/
HOURS(ChronoUnit.HOURS, "h", Duration::toHours),

/**
* Days.
*/
DAYS(ChronoUnit.DAYS, "d", Duration::toDays);

private final ChronoUnit chronoUnit;

private final String suffix;

private Function<Duration, Long> longValue;

Unit(ChronoUnit chronoUnit, String suffix, Function<Duration, Long> toUnit) {
this.chronoUnit = chronoUnit;
this.suffix = suffix;
this.longValue = toUnit;
}

public Duration parse(String value) {
return Duration.of(Long.valueOf(value), this.chronoUnit);
}

public long longValue(Duration value) {
return this.longValue.apply(value);
}

public static Unit fromChronoUnit(ChronoUnit chronoUnit) {
if (chronoUnit == null) {
return Unit.MILLIS;
}
for (Unit candidate : values()) {
if (candidate.chronoUnit == chronoUnit) {
return candidate;
}
}
throw new IllegalArgumentException("Unknown unit " + chronoUnit);
}

public static Unit fromSuffix(String suffix) {
for (Unit candidate : values()) {
if (candidate.suffix.equalsIgnoreCase(suffix)) {
return candidate;
}
}
throw new IllegalArgumentException("Unknown unit '" + suffix + "'");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
public @interface GeoSpatialIndexed {

/**
* Index name. <br />
* <br />
* Index name either as plain value or as {@link org.springframework.expression.spel.standard.SpelExpression template
* expression}. <br />
* The name will only be applied as is when defined on root level. For usage on nested or embedded structures the
* provided name will be prefixed with the path leading to the entity. <br />
* <br />
Expand All @@ -52,6 +52,7 @@
* &#64;Document
* class Hybrid {
* &#64;GeoSpatialIndexed(name="index") Point h1;
* &#64;GeoSpatialIndexed(name="#{&#64;myBean.indexName}") Point h2;
* }
*
* class Nested {
Expand All @@ -67,6 +68,7 @@
* db.root.createIndex( { hybrid.h1: "2d" } , { name: "hybrid.index" } )
* db.root.createIndex( { nested.n1: "2d" } , { name: "nested.index" } )
* db.hybrid.createIndex( { h1: "2d" } , { name: "index" } )
* db.hybrid.createIndex( { h2: "2d"} , { name: the value myBean.getIndexName() returned } )
* </code>
* </pre>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.core.index;

import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
Expand Down Expand Up @@ -116,6 +117,20 @@ public Index expire(long value) {
return expire(value, TimeUnit.SECONDS);
}

/**
* Specifies the TTL.
*
* @param timeout must not be {@literal null}.
* @return this.
* @throws IllegalArgumentException if given {@literal timeout} is {@literal null}.
* @since 2.2
*/
public Index expire(Duration timeout) {

Assert.notNull(timeout, "Timeout must not be null!");
return expire(timeout.getSeconds());
}

/**
* Specifies TTL with given {@link TimeUnit}.
*
Expand Down
Loading