Skip to content

Commit 8741b85

Browse files
committed
DATAMONGO-2112 - Polishing.
Align Indexed(expireAfter) default to empty string according to the documentation. Slightly reword Javadoc. Import DurationStyle to reuse duration parsing until Spring Framework provides a similar utility. Document ISO-8601 duration style. Prevent null index name evaluation to render "null" as String. Convert assertions from Hamcrest to AssertJ. Original pull request: #647.
1 parent 8fbaba0 commit 8741b85

File tree

6 files changed

+422
-196
lines changed

6 files changed

+422
-196
lines changed
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
* Copyright 2012-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.index;
17+
18+
import java.time.Duration;
19+
import java.time.temporal.ChronoUnit;
20+
import java.util.function.Function;
21+
import java.util.regex.Matcher;
22+
import java.util.regex.Pattern;
23+
24+
import org.springframework.lang.Nullable;
25+
import org.springframework.util.Assert;
26+
import org.springframework.util.StringUtils;
27+
28+
/**
29+
* Duration format styles.
30+
* <p/>
31+
* Fork of {@code org.springframework.boot.convert.DurationStyle}.
32+
*
33+
* @author Phillip Webb
34+
* @since 2.2
35+
*/
36+
enum DurationStyle {
37+
38+
/**
39+
* Simple formatting, for example '1s'.
40+
*/
41+
SIMPLE("^([\\+\\-]?\\d+)([a-zA-Z]{0,2})$") {
42+
43+
@Override
44+
public Duration parse(String value, @Nullable ChronoUnit unit) {
45+
try {
46+
Matcher matcher = matcher(value);
47+
Assert.state(matcher.matches(), "Does not match simple duration pattern");
48+
String suffix = matcher.group(2);
49+
return (StringUtils.hasLength(suffix) ? Unit.fromSuffix(suffix) : Unit.fromChronoUnit(unit))
50+
.parse(matcher.group(1));
51+
} catch (Exception ex) {
52+
throw new IllegalArgumentException("'" + value + "' is not a valid simple duration", ex);
53+
}
54+
}
55+
},
56+
57+
/**
58+
* ISO-8601 formatting.
59+
*/
60+
ISO8601("^[\\+\\-]?P.*$") {
61+
62+
@Override
63+
public Duration parse(String value, @Nullable ChronoUnit unit) {
64+
try {
65+
return Duration.parse(value);
66+
} catch (Exception ex) {
67+
throw new IllegalArgumentException("'" + value + "' is not a valid ISO-8601 duration", ex);
68+
}
69+
}
70+
};
71+
72+
private final Pattern pattern;
73+
74+
DurationStyle(String pattern) {
75+
this.pattern = Pattern.compile(pattern);
76+
}
77+
78+
protected final boolean matches(String value) {
79+
return this.pattern.matcher(value).matches();
80+
}
81+
82+
protected final Matcher matcher(String value) {
83+
return this.pattern.matcher(value);
84+
}
85+
86+
/**
87+
* Parse the given value to a duration.
88+
*
89+
* @param value the value to parse
90+
* @return a duration
91+
*/
92+
public Duration parse(String value) {
93+
return parse(value, null);
94+
}
95+
96+
/**
97+
* Parse the given value to a duration.
98+
*
99+
* @param value the value to parse
100+
* @param unit the duration unit to use if the value doesn't specify one ({@code null} will default to ms)
101+
* @return a duration
102+
*/
103+
public abstract Duration parse(String value, @Nullable ChronoUnit unit);
104+
105+
/**
106+
* Detect the style then parse the value to return a duration.
107+
*
108+
* @param value the value to parse
109+
* @return the parsed duration
110+
* @throws IllegalStateException if the value is not a known style or cannot be parsed
111+
*/
112+
public static Duration detectAndParse(String value) {
113+
return detectAndParse(value, null);
114+
}
115+
116+
/**
117+
* Detect the style then parse the value to return a duration.
118+
*
119+
* @param value the value to parse
120+
* @param unit the duration unit to use if the value doesn't specify one ({@code null} will default to ms)
121+
* @return the parsed duration
122+
* @throws IllegalStateException if the value is not a known style or cannot be parsed
123+
*/
124+
public static Duration detectAndParse(String value, @Nullable ChronoUnit unit) {
125+
return detect(value).parse(value, unit);
126+
}
127+
128+
/**
129+
* Detect the style from the given source value.
130+
*
131+
* @param value the source value
132+
* @return the duration style
133+
* @throws IllegalStateException if the value is not a known style
134+
*/
135+
public static DurationStyle detect(String value) {
136+
Assert.notNull(value, "Value must not be null");
137+
for (DurationStyle candidate : values()) {
138+
if (candidate.matches(value)) {
139+
return candidate;
140+
}
141+
}
142+
throw new IllegalArgumentException("'" + value + "' is not a valid duration");
143+
}
144+
145+
/**
146+
* Units that we support.
147+
*/
148+
enum Unit {
149+
150+
/**
151+
* Milliseconds.
152+
*/
153+
MILLIS(ChronoUnit.MILLIS, "ms", Duration::toMillis),
154+
155+
/**
156+
* Seconds.
157+
*/
158+
SECONDS(ChronoUnit.SECONDS, "s", Duration::getSeconds),
159+
160+
/**
161+
* Minutes.
162+
*/
163+
MINUTES(ChronoUnit.MINUTES, "m", Duration::toMinutes),
164+
165+
/**
166+
* Hours.
167+
*/
168+
HOURS(ChronoUnit.HOURS, "h", Duration::toHours),
169+
170+
/**
171+
* Days.
172+
*/
173+
DAYS(ChronoUnit.DAYS, "d", Duration::toDays);
174+
175+
private final ChronoUnit chronoUnit;
176+
177+
private final String suffix;
178+
179+
private Function<Duration, Long> longValue;
180+
181+
Unit(ChronoUnit chronoUnit, String suffix, Function<Duration, Long> toUnit) {
182+
this.chronoUnit = chronoUnit;
183+
this.suffix = suffix;
184+
this.longValue = toUnit;
185+
}
186+
187+
public Duration parse(String value) {
188+
return Duration.of(Long.valueOf(value), this.chronoUnit);
189+
}
190+
191+
public long longValue(Duration value) {
192+
return this.longValue.apply(value);
193+
}
194+
195+
public static Unit fromChronoUnit(ChronoUnit chronoUnit) {
196+
if (chronoUnit == null) {
197+
return Unit.MILLIS;
198+
}
199+
for (Unit candidate : values()) {
200+
if (candidate.chronoUnit == chronoUnit) {
201+
return candidate;
202+
}
203+
}
204+
throw new IllegalArgumentException("Unknown unit " + chronoUnit);
205+
}
206+
207+
public static Unit fromSuffix(String suffix) {
208+
for (Unit candidate : values()) {
209+
if (candidate.suffix.equalsIgnoreCase(suffix)) {
210+
return candidate;
211+
}
212+
}
213+
throw new IllegalArgumentException("Unknown unit '" + suffix + "'");
214+
}
215+
}
216+
}

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

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* @author Thomas Darimont
3131
* @author Christoph Strobl
3232
* @author Jordi Llach
33+
* @author Mark Paluch
3334
*/
3435
@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD })
3536
@Retention(RetentionPolicy.RUNTIME)
@@ -136,27 +137,32 @@
136137
int expireAfterSeconds() default -1;
137138

138139
/**
139-
* Alternative for {@link #expireAfterSeconds()} to configure the timeout after which the collection should expire.
140-
* Defaults to an empty String for no expiry. Accepts numeric values followed by their unit of measure (d(ays),
141-
* h(ours), m(inutes), s(seconds)) or a Spring {@literal template expression}. The expression can result in a a valid
142-
* expiration {@link String} following the conventions already mentioned or a {@link java.time.Duration}.
140+
* Alternative for {@link #expireAfterSeconds()} to configure the timeout after which the document should expire.
141+
* Defaults to an empty {@link String} for no expiry. Accepts numeric values followed by their unit of measure:
142+
* <ul>
143+
* <li><b>d</b>: Days</li>
144+
* <li><b>h</b>: Hours</li>
145+
* <li><b>m</b>: Minutes</li>
146+
* <li><b>s</b>: Seconds</li>
147+
* <li>Alternatively: A Spring {@literal template expression}. The expression can result in a
148+
* {@link java.time.Duration} or a valid expiration {@link String} according to the already mentioned
149+
* conventions.</li>
150+
* </ul>
151+
* Supports ISO-8601 style.
143152
*
144-
* <pre>
145-
* <code>
153+
* <pre class="code">
154+
*
155+
* &#0064;Indexed(expireAfter = "10s") String expireAfterTenSeconds;
146156
*
147-
* &#0064;Indexed(expireAfter = "10s")
148-
* String expireAfterTenSeconds;
157+
* &#0064;Indexed(expireAfter = "1d") String expireAfterOneDay;
149158
*
150-
* &#0064;Indexed(expireAfter = "1d")
151-
* String expireAfterOneDay;
159+
* &#0064;Indexed(expireAfter = "P2D") String expireAfterTwoDays;
152160
*
153-
* &#0064;Indexed(expireAfter = "#{&#0064;mySpringBean.timeout}")
154-
* String expireAfterTimeoutObtainedFromSpringBean;
155-
* </code>
161+
* &#0064;Indexed(expireAfter = "#{&#0064;mySpringBean.timeout}") String expireAfterTimeoutObtainedFromSpringBean;
156162
* </pre>
157163
*
158-
* @return {@literal 0s} by default.
164+
* @return empty by default.
159165
* @since 2.2
160166
*/
161-
String expireAfter() default "0s";
167+
String expireAfter() default "";
162168
}

0 commit comments

Comments
 (0)