|
19 | 19 | import lombok.EqualsAndHashCode;
|
20 | 20 | import lombok.RequiredArgsConstructor;
|
21 | 21 |
|
| 22 | +import java.time.Duration; |
22 | 23 | import java.util.ArrayList;
|
23 | 24 | import java.util.Arrays;
|
24 | 25 | import java.util.Collection;
|
|
28 | 29 | import java.util.List;
|
29 | 30 | import java.util.Set;
|
30 | 31 | import java.util.concurrent.TimeUnit;
|
| 32 | +import java.util.regex.Matcher; |
| 33 | +import java.util.regex.Pattern; |
31 | 34 | import java.util.stream.Collectors;
|
32 | 35 |
|
33 | 36 | import org.slf4j.Logger;
|
|
43 | 46 | import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexResolver.TextIndexIncludeOptions.IncludeStrategy;
|
44 | 47 | import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexDefinitionBuilder;
|
45 | 48 | import org.springframework.data.mongodb.core.index.TextIndexDefinition.TextIndexedFieldSpec;
|
| 49 | +import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity; |
46 | 50 | import org.springframework.data.mongodb.core.mapping.Document;
|
47 | 51 | import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
48 | 52 | import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
49 | 53 | import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
| 54 | +import org.springframework.data.spel.EvaluationContextProvider; |
50 | 55 | import org.springframework.data.util.TypeInformation;
|
| 56 | +import org.springframework.expression.EvaluationContext; |
| 57 | +import org.springframework.expression.Expression; |
| 58 | +import org.springframework.expression.ParserContext; |
| 59 | +import org.springframework.expression.common.LiteralExpression; |
| 60 | +import org.springframework.expression.spel.standard.SpelExpressionParser; |
51 | 61 | import org.springframework.lang.Nullable;
|
52 | 62 | import org.springframework.util.Assert;
|
53 | 63 | import org.springframework.util.ClassUtils;
|
| 64 | +import org.springframework.util.NumberUtils; |
54 | 65 | import org.springframework.util.StringUtils;
|
55 | 66 |
|
56 | 67 | /**
|
|
68 | 79 | public class MongoPersistentEntityIndexResolver implements IndexResolver {
|
69 | 80 |
|
70 | 81 | private static final Logger LOGGER = LoggerFactory.getLogger(MongoPersistentEntityIndexResolver.class);
|
| 82 | + private static final Pattern TIMEOUT_PATTERN = Pattern.compile("(\\d+)(\\W+)?([dhms])"); |
| 83 | + private static final SpelExpressionParser PARSER = new SpelExpressionParser(); |
71 | 84 |
|
72 | 85 | private final MongoMappingContext mappingContext;
|
| 86 | + private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT; |
73 | 87 |
|
74 | 88 | /**
|
75 | 89 | * Create new {@link MongoPersistentEntityIndexResolver}.
|
@@ -428,9 +442,54 @@ protected IndexDefinitionHolder createIndexDefinition(String dotPath, String col
|
428 | 442 | indexDefinition.expire(index.expireAfterSeconds(), TimeUnit.SECONDS);
|
429 | 443 | }
|
430 | 444 |
|
| 445 | + if (!index.expireAfter().isEmpty() && !index.expireAfter().equals("0s")) { |
| 446 | + |
| 447 | + if (index.expireAfterSeconds() >= 0) { |
| 448 | + throw new IllegalStateException(String.format( |
| 449 | + "@Indexed already defines an expiration timeout of %s sec. via Indexed#expireAfterSeconds. Please make to use either expireAfterSeconds or expireAfter.", index.expireAfterSeconds())); |
| 450 | + } |
| 451 | + |
| 452 | + EvaluationContext ctx = getEvaluationContext(); |
| 453 | + |
| 454 | + if (persitentProperty.getOwner() instanceof BasicMongoPersistentEntity) { |
| 455 | + |
| 456 | + EvaluationContext contextFromEntity = ((BasicMongoPersistentEntity<?>) persitentProperty.getOwner()) |
| 457 | + .getEvaluationContext(null); |
| 458 | + if (contextFromEntity != null && !EvaluationContextProvider.DEFAULT.equals(contextFromEntity)) { |
| 459 | + ctx = contextFromEntity; |
| 460 | + } |
| 461 | + } |
| 462 | + |
| 463 | + Duration timeout = computeIndexTimeout(index.expireAfter(), ctx); |
| 464 | + if (!timeout.isZero() && !timeout.isNegative()) { |
| 465 | + indexDefinition.expire(timeout); |
| 466 | + } |
| 467 | + } |
| 468 | + |
431 | 469 | return new IndexDefinitionHolder(dotPath, indexDefinition, collection);
|
432 | 470 | }
|
433 | 471 |
|
| 472 | + /** |
| 473 | + * Get the default {@link EvaluationContext}. |
| 474 | + * |
| 475 | + * @return never {@literal null}. |
| 476 | + * @since 2.2 |
| 477 | + */ |
| 478 | + protected EvaluationContext getEvaluationContext() { |
| 479 | + return evaluationContextProvider.getEvaluationContext(null); |
| 480 | + } |
| 481 | + |
| 482 | + /** |
| 483 | + * Set the {@link EvaluationContextProvider} used for obtaining the {@link EvaluationContext} used to compute |
| 484 | + * {@link org.springframework.expression.spel.standard.SpelExpression expressions}. |
| 485 | + * |
| 486 | + * @param evaluationContextProvider must not be {@literal null}. |
| 487 | + * @since 2.2 |
| 488 | + */ |
| 489 | + public void setEvaluationContextProvider(EvaluationContextProvider evaluationContextProvider) { |
| 490 | + this.evaluationContextProvider = evaluationContextProvider; |
| 491 | + } |
| 492 | + |
434 | 493 | /**
|
435 | 494 | * Creates {@link IndexDefinition} wrapped in {@link IndexDefinitionHolder} out of {@link GeoSpatialIndexed} for
|
436 | 495 | * {@link MongoPersistentProperty}.
|
@@ -511,6 +570,58 @@ private void resolveAndAddIndexesForAssociation(Association<MongoPersistentPrope
|
511 | 570 | }
|
512 | 571 | }
|
513 | 572 |
|
| 573 | + /** |
| 574 | + * Compute the index timeout value by evaluating a potential |
| 575 | + * {@link org.springframework.expression.spel.standard.SpelExpression} and parsing the final value. |
| 576 | + * |
| 577 | + * @param timeoutValue must not be {@literal null}. |
| 578 | + * @param evaluationContext must not be {@literal null}. |
| 579 | + * @return never {@literal null} |
| 580 | + * @since 2.2 |
| 581 | + * @throws IllegalArgumentException for invalid duration values. |
| 582 | + */ |
| 583 | + private static Duration computeIndexTimeout(String timeoutValue, EvaluationContext evaluationContext) { |
| 584 | + |
| 585 | + String val = evaluatePotentialTemplateExpression(timeoutValue, evaluationContext); |
| 586 | + |
| 587 | + if (val == null) { |
| 588 | + return Duration.ZERO; |
| 589 | + } |
| 590 | + |
| 591 | + Matcher matcher = TIMEOUT_PATTERN.matcher(val); |
| 592 | + if (matcher.find()) { |
| 593 | + |
| 594 | + Long timeout = NumberUtils.parseNumber(matcher.group(1), Long.class); |
| 595 | + String unit = matcher.group(3); |
| 596 | + |
| 597 | + switch (unit) { |
| 598 | + case "d": |
| 599 | + return Duration.ofDays(timeout); |
| 600 | + case "h": |
| 601 | + return Duration.ofHours(timeout); |
| 602 | + case "m": |
| 603 | + return Duration.ofMinutes(timeout); |
| 604 | + case "s": |
| 605 | + return Duration.ofSeconds(timeout); |
| 606 | + } |
| 607 | + } |
| 608 | + |
| 609 | + throw new IllegalArgumentException( |
| 610 | + String.format("Index timeout %s cannot be parsed. Please use the following pattern '\\d+\\W?[dhms]'.", val)); |
| 611 | + } |
| 612 | + |
| 613 | + @Nullable |
| 614 | + private static String evaluatePotentialTemplateExpression(String value, EvaluationContext evaluationContext) { |
| 615 | + |
| 616 | + Expression expression = PARSER.parseExpression(value, ParserContext.TEMPLATE_EXPRESSION); |
| 617 | + if (expression instanceof LiteralExpression) { |
| 618 | + return value; |
| 619 | + } |
| 620 | + |
| 621 | + return expression.getValue(evaluationContext, String.class); |
| 622 | + |
| 623 | + } |
| 624 | + |
514 | 625 | /**
|
515 | 626 | * {@link CycleGuard} holds information about properties and the paths for accessing those. This information is used
|
516 | 627 | * to detect potential cycles within the references.
|
|
0 commit comments