15
15
*/
16
16
package org .springframework .data .mongodb .core ;
17
17
18
+ import java .time .Duration ;
18
19
import java .util .Collection ;
19
20
import java .util .Iterator ;
20
21
import java .util .LinkedHashMap ;
21
22
import java .util .Map ;
22
23
import java .util .Optional ;
24
+ import java .util .concurrent .TimeUnit ;
23
25
24
26
import org .bson .BsonNull ;
25
27
import org .bson .Document ;
40
42
import org .springframework .data .mongodb .core .convert .MongoWriter ;
41
43
import org .springframework .data .mongodb .core .convert .QueryMapper ;
42
44
import org .springframework .data .mongodb .core .mapping .FieldName ;
43
- import org .springframework .data .mongodb .core .mapping .MongoPersistentEntity ;
44
- import org .springframework .data .mongodb .core .mapping .MongoPersistentProperty ;
45
- import org .springframework .data .mongodb .core .mapping .MongoSimpleTypes ;
46
- import org .springframework .data .mongodb .core .mapping .TimeSeries ;
45
+ import org .springframework .data .mongodb .core .index .DurationStyle ;
46
+ import org .springframework .data .mongodb .core .mapping .*;
47
47
import org .springframework .data .mongodb .core .query .Collation ;
48
48
import org .springframework .data .mongodb .core .query .Criteria ;
49
49
import org .springframework .data .mongodb .core .query .Query ;
54
54
import org .springframework .data .projection .EntityProjectionIntrospector ;
55
55
import org .springframework .data .projection .ProjectionFactory ;
56
56
import org .springframework .data .projection .TargetAware ;
57
+ import org .springframework .data .spel .EvaluationContextProvider ;
57
58
import org .springframework .data .util .Optionals ;
59
+ import org .springframework .expression .EvaluationContext ;
60
+ import org .springframework .expression .Expression ;
61
+ import org .springframework .expression .ParserContext ;
62
+ import org .springframework .expression .common .LiteralExpression ;
63
+ import org .springframework .expression .spel .standard .SpelExpressionParser ;
58
64
import org .springframework .lang .Nullable ;
59
65
import org .springframework .util .Assert ;
60
66
import org .springframework .util .ClassUtils ;
74
80
* @author Oliver Gierke
75
81
* @author Mark Paluch
76
82
* @author Christoph Strobl
83
+ * @author Ben Foster
77
84
* @since 2.1
78
85
* @see MongoTemplate
79
86
* @see ReactiveMongoTemplate
@@ -89,6 +96,8 @@ class EntityOperations {
89
96
90
97
private final MongoJsonSchemaMapper schemaMapper ;
91
98
99
+ private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider .DEFAULT ;
100
+
92
101
EntityOperations (MongoConverter converter ) {
93
102
this (converter , new QueryMapper (converter ));
94
103
}
@@ -276,7 +285,7 @@ public <T> TypedOperations<T> forType(@Nullable Class<T> entityClass) {
276
285
MongoPersistentEntity <?> entity = context .getPersistentEntity (entityClass );
277
286
278
287
if (entity != null ) {
279
- return new TypedEntityOperations (entity );
288
+ return new TypedEntityOperations (entity , evaluationContextProvider );
280
289
}
281
290
282
291
}
@@ -354,6 +363,10 @@ public CreateCollectionOptions convertToCreateCollectionOptions(@Nullable Collec
354
363
options .granularity (TimeSeriesGranularity .valueOf (it .getGranularity ().name ().toUpperCase ()));
355
364
}
356
365
366
+ if (it .getExpireAfterSeconds () >= 0 ) {
367
+ result .expireAfter (it .getExpireAfterSeconds (), TimeUnit .SECONDS );
368
+ }
369
+
357
370
result .timeSeriesOptions (options );
358
371
});
359
372
@@ -1026,10 +1039,13 @@ public TimeSeriesOptions mapTimeSeriesOptions(TimeSeriesOptions options) {
1026
1039
*/
1027
1040
static class TypedEntityOperations <T > implements TypedOperations <T > {
1028
1041
1042
+ private static final SpelExpressionParser PARSER = new SpelExpressionParser ();
1029
1043
private final MongoPersistentEntity <T > entity ;
1044
+ private final EvaluationContextProvider evaluationContextProvider ;
1030
1045
1031
- protected TypedEntityOperations (MongoPersistentEntity <T > entity ) {
1046
+ protected TypedEntityOperations (MongoPersistentEntity <T > entity , EvaluationContextProvider evaluationContextProvider ) {
1032
1047
this .entity = entity ;
1048
+ this .evaluationContextProvider = evaluationContextProvider ;
1033
1049
}
1034
1050
1035
1051
@ Override
@@ -1077,6 +1093,26 @@ public CollectionOptions getCollectionOptions() {
1077
1093
if (!Granularity .DEFAULT .equals (timeSeries .granularity ())) {
1078
1094
options = options .granularity (timeSeries .granularity ());
1079
1095
}
1096
+
1097
+ if (timeSeries .expireAfterSeconds () >= 0 ) {
1098
+ options = options .expireAfter (Duration .ofSeconds (timeSeries .expireAfterSeconds ()));
1099
+ }
1100
+
1101
+ if (StringUtils .hasText (timeSeries .expireAfter ())) {
1102
+
1103
+ if (timeSeries .expireAfterSeconds () >= 0 ) {
1104
+ throw new IllegalStateException (String .format (
1105
+ "@TimeSeries already defines an expiration timeout of %s seconds via TimeSeries#expireAfterSeconds; Please make to use either expireAfterSeconds or expireAfter" ,
1106
+ timeSeries .expireAfterSeconds ()));
1107
+ }
1108
+
1109
+ Duration timeout = computeIndexTimeout (timeSeries .expireAfter (),
1110
+ getEvaluationContextForProperty (entity ));
1111
+ if (!timeout .isZero () && !timeout .isNegative ()) {
1112
+ options = options .expireAfter (timeout );
1113
+ }
1114
+ }
1115
+
1080
1116
collectionOptions = collectionOptions .timeSeries (options );
1081
1117
}
1082
1118
@@ -1091,7 +1127,8 @@ public TimeSeriesOptions mapTimeSeriesOptions(TimeSeriesOptions source) {
1091
1127
if (StringUtils .hasText (source .getMetaField ())) {
1092
1128
target = target .metaField (mappedNameOrDefault (source .getMetaField ()));
1093
1129
}
1094
- return target .granularity (source .getGranularity ());
1130
+ return target .granularity (source .getGranularity ())
1131
+ .expireAfter (Duration .ofSeconds (source .getExpireAfterSeconds ()));
1095
1132
}
1096
1133
1097
1134
private String mappedNameOrDefault (String name ) {
@@ -1105,4 +1142,90 @@ public String getIdKeyName() {
1105
1142
}
1106
1143
}
1107
1144
1145
+
1146
+ /**
1147
+ * Compute the index timeout value by evaluating a potential
1148
+ * {@link org.springframework.expression.spel.standard.SpelExpression} and parsing the final value.
1149
+ *
1150
+ * @param timeoutValue must not be {@literal null}.
1151
+ * @param evaluationContext must not be {@literal null}.
1152
+ * @return never {@literal null}
1153
+ * @since 2.2
1154
+ * @throws IllegalArgumentException for invalid duration values.
1155
+ */
1156
+ private static Duration computeIndexTimeout (String timeoutValue , EvaluationContext evaluationContext ) {
1157
+
1158
+ Object evaluatedTimeout = evaluate (timeoutValue , evaluationContext );
1159
+
1160
+ if (evaluatedTimeout == null ) {
1161
+ return Duration .ZERO ;
1162
+ }
1163
+
1164
+ if (evaluatedTimeout instanceof Duration ) {
1165
+ return (Duration ) evaluatedTimeout ;
1166
+ }
1167
+
1168
+ String val = evaluatedTimeout .toString ();
1169
+
1170
+ if (val == null ) {
1171
+ return Duration .ZERO ;
1172
+ }
1173
+
1174
+ return DurationStyle .detectAndParse (val );
1175
+ }
1176
+
1177
+ @ Nullable
1178
+ private static Object evaluate (String value , EvaluationContext evaluationContext ) {
1179
+
1180
+ Expression expression = PARSER .parseExpression (value , ParserContext .TEMPLATE_EXPRESSION );
1181
+ if (expression instanceof LiteralExpression ) {
1182
+ return value ;
1183
+ }
1184
+
1185
+ return expression .getValue (evaluationContext , Object .class );
1186
+ }
1187
+
1188
+
1189
+ /**
1190
+ * Get the {@link EvaluationContext} for a given {@link PersistentEntity entity} the default one.
1191
+ *
1192
+ * @param persistentEntity can be {@literal null}
1193
+ * @return
1194
+ */
1195
+ private EvaluationContext getEvaluationContextForProperty (@ Nullable PersistentEntity <?, ?> persistentEntity ) {
1196
+
1197
+ if (!(persistentEntity instanceof BasicMongoPersistentEntity )) {
1198
+ return getEvaluationContext ();
1199
+ }
1200
+
1201
+ EvaluationContext contextFromEntity = ((BasicMongoPersistentEntity <?>) persistentEntity ).getEvaluationContext (null );
1202
+
1203
+ if (!EvaluationContextProvider .DEFAULT .equals (contextFromEntity )) {
1204
+ return contextFromEntity ;
1205
+ }
1206
+
1207
+ return getEvaluationContext ();
1208
+ }
1209
+
1210
+ /**
1211
+ * Get the default {@link EvaluationContext}.
1212
+ *
1213
+ * @return never {@literal null}.
1214
+ * @since 2.2
1215
+ */
1216
+ protected EvaluationContext getEvaluationContext () {
1217
+ return evaluationContextProvider .getEvaluationContext (null );
1218
+ }
1219
+ }
1220
+
1221
+ /**
1222
+ * Set the {@link EvaluationContextProvider} used for obtaining the {@link EvaluationContext} used to compute
1223
+ * {@link org.springframework.expression.spel.standard.SpelExpression expressions}.
1224
+ *
1225
+ * @param evaluationContextProvider must not be {@literal null}.
1226
+ * @since 2.2
1227
+ */
1228
+ public void setEvaluationContextProvider (EvaluationContextProvider evaluationContextProvider ) {
1229
+ this .evaluationContextProvider = evaluationContextProvider ;
1230
+ }
1108
1231
}
0 commit comments