From eb46251e85634eb069dcd1e2d8dd9df5ae8b8c15 Mon Sep 17 00:00:00 2001 From: krasaev Date: Fri, 16 Apr 2021 13:00:57 +0300 Subject: [PATCH] add ttl index annotation support (#215) * add ttl index annotation support * TtlIndexed tests * TtlIndex Co-authored-by: Michele Rastelli --- .../springframework/annotation/TtlIndex.java | 49 ++++++++++ .../annotation/TtlIndexed.java | 24 +++++ .../core/CollectionOperations.java | 13 +++ .../core/mapping/ArangoPersistentEntity.java | 5 + .../mapping/ArangoPersistentProperty.java | 2 + .../DefaultArangoPersistentEntity.java | 78 +++++++++++----- .../DefaultArangoPersistentProperty.java | 6 ++ .../core/template/ArangoTemplate.java | 72 ++++++++------ .../template/DefaultCollectionOperations.java | 10 ++ .../core/template/ArangoIndexTest.java | 93 +++++++++++++++++-- 10 files changed, 288 insertions(+), 64 deletions(-) create mode 100644 src/main/java/com/arangodb/springframework/annotation/TtlIndex.java create mode 100644 src/main/java/com/arangodb/springframework/annotation/TtlIndexed.java diff --git a/src/main/java/com/arangodb/springframework/annotation/TtlIndex.java b/src/main/java/com/arangodb/springframework/annotation/TtlIndex.java new file mode 100644 index 000000000..1eca0ba08 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/annotation/TtlIndex.java @@ -0,0 +1,49 @@ +/* + * DISCLAIMER + * + * Copyright 2017 ArangoDB GmbH, Cologne, Germany + * + * 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 + * + * http://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. + * + * Copyright holder is ArangoDB GmbH, Cologne, Germany + */ + +package com.arangodb.springframework.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to define given field to be indexed using ArangoDB's TTL index. + * + * @author Michele Rastelli + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface TtlIndex { + + /** + * attribute path + */ + String field(); + + /** + * The time interval (in seconds) from the point in time of the value of the field specified in {@link this#field()} + * after which the documents count as expired. Default is 0, which means that the documents expire as soon as the + * server time passes the point in time stored in the document attribute. + */ + int expireAfter() default 0; + +} diff --git a/src/main/java/com/arangodb/springframework/annotation/TtlIndexed.java b/src/main/java/com/arangodb/springframework/annotation/TtlIndexed.java new file mode 100644 index 000000000..51531d6d0 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/annotation/TtlIndexed.java @@ -0,0 +1,24 @@ +package com.arangodb.springframework.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark a field to be indexed using ArangoDB's Ttl index. + * + * @author Dmitry Krasaev + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface TtlIndexed { + + /** + * The time interval (in seconds) from the point in time of the value of the annotated field after which the + * documents count as expired. Default is 0, which means that the documents expire as soon as the server time passes + * the point in time stored in the document attribute. + */ + int expireAfter() default 0; + +} diff --git a/src/main/java/com/arangodb/springframework/core/CollectionOperations.java b/src/main/java/com/arangodb/springframework/core/CollectionOperations.java index 18dc4c65d..74cf02c9c 100644 --- a/src/main/java/com/arangodb/springframework/core/CollectionOperations.java +++ b/src/main/java/com/arangodb/springframework/core/CollectionOperations.java @@ -32,6 +32,7 @@ import com.arangodb.model.HashIndexOptions; import com.arangodb.model.PersistentIndexOptions; import com.arangodb.model.SkiplistIndexOptions; +import com.arangodb.model.TtlIndexOptions; /** * Interface that specifies a basic set of ArangoDB operations on collection @@ -148,6 +149,18 @@ IndexEntity ensurePersistentIndex(Iterable fields, PersistentIndexOption */ IndexEntity ensureFulltextIndex(Iterable fields, FulltextIndexOptions options) throws DataAccessException; + /** + * Creates a ttl index for the collection, if it does not already exist. + * + * @param fields + * A list of attribute paths + * @param options + * Additional options, can be null + * @return information about the index + * @throws DataAccessException + */ + IndexEntity ensureTtlIndex(Iterable fields, TtlIndexOptions options) throws DataAccessException; + /** * Deletes the index with the given {@code id} from the collection. * diff --git a/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentEntity.java b/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentEntity.java index 8784fa6ea..d5fee02a9 100644 --- a/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentEntity.java +++ b/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentEntity.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.Optional; +import com.arangodb.springframework.annotation.TtlIndex; import org.springframework.context.ApplicationContextAware; import org.springframework.data.mapping.IdentifierAccessor; import org.springframework.data.mapping.PersistentEntity; @@ -60,6 +61,8 @@ public interface ArangoPersistentEntity Collection getFulltextIndexes(); + Optional getTtlIndex(); + Collection getHashIndexedProperties(); Collection getSkiplistIndexedProperties(); @@ -70,6 +73,8 @@ public interface ArangoPersistentEntity Collection getFulltextIndexedProperties(); + Optional getTtlIndexedProperty(); + IdentifierAccessor getArangoIdAccessor(Object bean); } diff --git a/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentProperty.java b/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentProperty.java index 9f8fb36c7..122b948da 100644 --- a/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentProperty.java +++ b/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentProperty.java @@ -33,6 +33,7 @@ import com.arangodb.springframework.annotation.Relations; import com.arangodb.springframework.annotation.SkiplistIndexed; import com.arangodb.springframework.annotation.To; +import com.arangodb.springframework.annotation.TtlIndexed; /** * @author Mark Vollmary @@ -64,4 +65,5 @@ public interface ArangoPersistentProperty extends PersistentProperty getFulltextIndexed(); + Optional getTtlIndexed(); } diff --git a/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentEntity.java b/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentEntity.java index 223daf575..d9d09d305 100644 --- a/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentEntity.java +++ b/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentEntity.java @@ -20,23 +20,28 @@ package com.arangodb.springframework.core.mapping; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - +import com.arangodb.entity.CollectionType; +import com.arangodb.model.CollectionCreateOptions; +import com.arangodb.springframework.annotation.Document; +import com.arangodb.springframework.annotation.Edge; +import com.arangodb.springframework.annotation.FulltextIndex; +import com.arangodb.springframework.annotation.FulltextIndexes; +import com.arangodb.springframework.annotation.GeoIndex; +import com.arangodb.springframework.annotation.GeoIndexes; +import com.arangodb.springframework.annotation.HashIndex; +import com.arangodb.springframework.annotation.HashIndexes; +import com.arangodb.springframework.annotation.PersistentIndex; +import com.arangodb.springframework.annotation.PersistentIndexes; +import com.arangodb.springframework.annotation.SkiplistIndex; +import com.arangodb.springframework.annotation.SkiplistIndexes; +import com.arangodb.springframework.annotation.TtlIndex; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.expression.BeanFactoryAccessor; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.data.mapping.IdentifierAccessor; +import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.TargetAwareIdentifierAccessor; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.util.TypeInformation; @@ -47,20 +52,16 @@ import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; -import com.arangodb.entity.CollectionType; -import com.arangodb.model.CollectionCreateOptions; -import com.arangodb.springframework.annotation.Document; -import com.arangodb.springframework.annotation.Edge; -import com.arangodb.springframework.annotation.FulltextIndex; -import com.arangodb.springframework.annotation.FulltextIndexes; -import com.arangodb.springframework.annotation.GeoIndex; -import com.arangodb.springframework.annotation.GeoIndexes; -import com.arangodb.springframework.annotation.HashIndex; -import com.arangodb.springframework.annotation.HashIndexes; -import com.arangodb.springframework.annotation.PersistentIndex; -import com.arangodb.springframework.annotation.PersistentIndexes; -import com.arangodb.springframework.annotation.SkiplistIndex; -import com.arangodb.springframework.annotation.SkiplistIndexes; +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; /** * @author Mark Vollmary @@ -78,6 +79,7 @@ public class DefaultArangoPersistentEntity extends BasicPersistentEntity hashIndexedProperties; private final Collection skiplistIndexedProperties; private final Collection persistentIndexedProperties; @@ -187,11 +189,23 @@ public void setApplicationContext(final ApplicationContext applicationContext) t public void addPersistentProperty(final ArangoPersistentProperty property) { super.addPersistentProperty(property); if (property.isArangoIdProperty()) { + if (arangoIdProperty != null) { + throw new MappingException("Found multiple id indexed properties!"); + } arangoIdProperty = property; } if (property.isRevProperty()) { + if (revProperty != null) { + throw new MappingException("Found multiple rev indexed properties!"); + } revProperty = property; } + if (property.getTtlIndexed().isPresent()) { + if (ttlIndexedProperty != null) { + throw new MappingException("Found multiple ttl indexed properties!"); + } + ttlIndexedProperty = property; + } property.getHashIndexed().ifPresent(i -> hashIndexedProperties.add(property)); property.getSkiplistIndexed().ifPresent(i -> skiplistIndexedProperties.add(property)); property.getPersistentIndexed().ifPresent(i -> persistentIndexedProperties.add(property)); @@ -252,6 +266,15 @@ public Collection getFulltextIndexes() { return indexes; } + @Override + public Optional getTtlIndex() { + return getIndex(TtlIndex.class); + } + + private Optional getIndex(final Class annotation) { + return Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(getType(), annotation)); + } + public Collection getIndexes(final Class annotation) { final List indexes = findAnnotations(annotation).stream().filter(a -> annotation.isInstance(a)) .map(a -> annotation.cast(a)).collect(Collectors.toList()); @@ -283,6 +306,11 @@ public Collection getFulltextIndexedProperties() { return fulltextIndexedProperties; } + @Override + public Optional getTtlIndexedProperty() { + return Optional.ofNullable(ttlIndexedProperty); + } + @SuppressWarnings("unchecked") public Set findAnnotations(final Class annotationType) { return (Set) repeatableAnnotationCache.computeIfAbsent(annotationType, diff --git a/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentProperty.java b/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentProperty.java index 6129413e5..c37541747 100644 --- a/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentProperty.java +++ b/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentProperty.java @@ -43,6 +43,7 @@ import com.arangodb.springframework.annotation.Rev; import com.arangodb.springframework.annotation.SkiplistIndexed; import com.arangodb.springframework.annotation.To; +import com.arangodb.springframework.annotation.TtlIndexed; /** * @author Mark Vollmary @@ -145,4 +146,9 @@ public Optional getFulltextIndexed() { return Optional.ofNullable(findAnnotation(FulltextIndexed.class)); } + @Override + public Optional getTtlIndexed() { + return Optional.ofNullable(findAnnotation(TtlIndexed.class)); + } + } diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java index cfa837b12..b1fe057b9 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -20,34 +20,6 @@ package com.arangodb.springframework.core.template; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import org.springframework.beans.BeansException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.expression.BeanFactoryAccessor; -import org.springframework.context.expression.BeanFactoryResolver; -import org.springframework.dao.DataAccessException; -import org.springframework.dao.support.PersistenceExceptionTranslator; -import org.springframework.data.domain.Persistable; -import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.expression.Expression; -import org.springframework.expression.ParserContext; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; - import com.arangodb.ArangoCollection; import com.arangodb.ArangoCursor; import com.arangodb.ArangoDB; @@ -69,11 +41,13 @@ import com.arangodb.model.HashIndexOptions; import com.arangodb.model.PersistentIndexOptions; import com.arangodb.model.SkiplistIndexOptions; +import com.arangodb.model.TtlIndexOptions; import com.arangodb.springframework.annotation.FulltextIndex; import com.arangodb.springframework.annotation.GeoIndex; import com.arangodb.springframework.annotation.HashIndex; import com.arangodb.springframework.annotation.PersistentIndex; import com.arangodb.springframework.annotation.SkiplistIndex; +import com.arangodb.springframework.annotation.TtlIndex; import com.arangodb.springframework.core.ArangoOperations; import com.arangodb.springframework.core.CollectionOperations; import com.arangodb.springframework.core.UserOperations; @@ -92,6 +66,33 @@ import com.arangodb.springframework.core.util.MetadataUtils; import com.arangodb.util.MapBuilder; import com.arangodb.velocypack.VPackSlice; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.expression.BeanFactoryAccessor; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.domain.Persistable; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.expression.Expression; +import org.springframework.expression.ParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; /** * @author Mark Vollmary @@ -213,6 +214,8 @@ private static void ensureCollectionIndexes(final CollectionOperations collectio persistentEntity.getGeoIndexedProperties().stream().forEach(p -> ensureGeoIndex(collection, p)); persistentEntity.getFulltextIndexes().stream().forEach(index -> ensureFulltextIndex(collection, index)); persistentEntity.getFulltextIndexedProperties().stream().forEach(p -> ensureFulltextIndex(collection, p)); + persistentEntity.getTtlIndex().ifPresent(index -> ensureTtlIndex(collection, index)); + persistentEntity.getTtlIndexedProperty().ifPresent(p -> ensureTtlIndex(collection, p)); } private static void ensureHashIndex(final CollectionOperations collection, final HashIndex annotation) { @@ -269,12 +272,23 @@ private static void ensureFulltextIndex(final CollectionOperations collection, f } private static void ensureFulltextIndex(final CollectionOperations collection, - final ArangoPersistentProperty value) { + final ArangoPersistentProperty value) { final FulltextIndexOptions options = new FulltextIndexOptions(); value.getFulltextIndexed().ifPresent(i -> options.minLength(i.minLength() > -1 ? i.minLength() : null)); collection.ensureFulltextIndex(Collections.singleton(value.getFieldName()), options); } + private static void ensureTtlIndex(final CollectionOperations collection, final TtlIndex annotation) { + collection.ensureTtlIndex(Collections.singleton(annotation.field()), + new TtlIndexOptions().expireAfter(annotation.expireAfter())); + } + + private static void ensureTtlIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { + final TtlIndexOptions options = new TtlIndexOptions(); + value.getTtlIndexed().ifPresent(i -> options.expireAfter(i.expireAfter())); + collection.ensureTtlIndex(Collections.singleton(value.getFieldName()), options); + } + private Optional determineCollectionFromId(final Object id) { return id != null ? Optional.ofNullable(MetadataUtils.determineCollectionFromId(converter.convertId(id))) : Optional.empty(); diff --git a/src/main/java/com/arangodb/springframework/core/template/DefaultCollectionOperations.java b/src/main/java/com/arangodb/springframework/core/template/DefaultCollectionOperations.java index abb01ea8d..a4cfce5a1 100644 --- a/src/main/java/com/arangodb/springframework/core/template/DefaultCollectionOperations.java +++ b/src/main/java/com/arangodb/springframework/core/template/DefaultCollectionOperations.java @@ -36,6 +36,7 @@ import com.arangodb.model.HashIndexOptions; import com.arangodb.model.PersistentIndexOptions; import com.arangodb.model.SkiplistIndexOptions; +import com.arangodb.model.TtlIndexOptions; import com.arangodb.springframework.core.CollectionOperations; /** @@ -162,6 +163,15 @@ public IndexEntity ensureFulltextIndex(final Iterable fields, final Full } } + @Override + public IndexEntity ensureTtlIndex(Iterable fields, TtlIndexOptions options) throws DataAccessException { + try { + return collection.ensureTtlIndex(fields, options); + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + @Override public void dropIndex(final String id) throws DataAccessException { try { diff --git a/src/test/java/com/arangodb/springframework/core/template/ArangoIndexTest.java b/src/test/java/com/arangodb/springframework/core/template/ArangoIndexTest.java index b6ca7f00e..15db43f25 100644 --- a/src/test/java/com/arangodb/springframework/core/template/ArangoIndexTest.java +++ b/src/test/java/com/arangodb/springframework/core/template/ArangoIndexTest.java @@ -20,14 +20,7 @@ package com.arangodb.springframework.core.template; -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -import java.util.stream.Collectors; - -import org.junit.Test; - +import com.arangodb.entity.IndexEntity; import com.arangodb.entity.IndexType; import com.arangodb.springframework.AbstractArangoTest; import com.arangodb.springframework.annotation.Document; @@ -46,6 +39,17 @@ import com.arangodb.springframework.annotation.SkiplistIndex; import com.arangodb.springframework.annotation.SkiplistIndexed; import com.arangodb.springframework.annotation.SkiplistIndexes; +import com.arangodb.springframework.annotation.TtlIndex; +import com.arangodb.springframework.annotation.TtlIndexed; +import org.junit.Test; +import org.springframework.data.mapping.MappingException; + +import java.util.Collection; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.fail; /** * @author Mark Vollmary @@ -504,17 +508,18 @@ public static class DifferentIndexedAnnotations { @PersistentIndexed @GeoIndexed @FulltextIndexed + @TtlIndexed private String a; } @Test public void differentIndexedAnnotationsSameField() { - assertThat(template.collection(DifferentIndexedAnnotations.class).getIndexes().size(), is(6)); + assertThat(template.collection(DifferentIndexedAnnotations.class).getIndexes().size(), is(7)); assertThat( template.collection(DifferentIndexedAnnotations.class).getIndexes().stream().map(i -> i.getType()) .collect(Collectors.toList()), hasItems(IndexType.primary, IndexType.hash, IndexType.skiplist, IndexType.persistent, geo1(), - IndexType.fulltext)); + IndexType.fulltext, IndexType.ttl)); } @HashIndex(fields = { "a" }) @@ -586,4 +591,72 @@ public void twoEntityCollectionWithAdditionalIndexes() { is(1 + 3)); } + public static class TtlIndexedSingleFieldTestEntity { + @TtlIndexed + private String a; + } + + @Test + public void singleFieldTtlIndexed() { + Collection indexes = template.collection(TtlIndexedSingleFieldTestEntity.class).getIndexes(); + assertThat(indexes, hasSize(2)); + assertThat(indexes.stream().map(IndexEntity::getType).collect(Collectors.toList()), + hasItems(IndexType.primary, IndexType.ttl)); + IndexEntity ttlIdx = indexes.stream().filter(i -> i.getType() == IndexType.ttl).findFirst().get(); + assertThat(ttlIdx.getFields(), hasSize(1)); + assertThat(ttlIdx.getFields(), hasItems("a")); + assertThat(ttlIdx.getExpireAfter(), is(0)); + } + + public static class TtlIndexedExpireAfterTestEntity { + @TtlIndexed(expireAfter = 3600) + private String a; + } + + @Test + public void expireAfterTtlIndexed() { + Collection indexes = template.collection(TtlIndexedExpireAfterTestEntity.class).getIndexes(); + assertThat(indexes, hasSize(2)); + assertThat(indexes.stream().map(IndexEntity::getType).collect(Collectors.toList()), + hasItems(IndexType.primary, IndexType.ttl)); + IndexEntity ttlIdx = indexes.stream().filter(i -> i.getType() == IndexType.ttl).findFirst().get(); + assertThat(ttlIdx.getFields(), hasSize(1)); + assertThat(ttlIdx.getFields(), hasItems("a")); + assertThat(ttlIdx.getExpireAfter(), is(3600)); + } + + public static class MultipleTtlIndexedTestEntity { + @TtlIndexed + private String a; + + @TtlIndexed + private String b; + } + + @Test + public void multipleTtlIndexedShouldThrow() { + try { + template.collection(MultipleTtlIndexedTestEntity.class).getIndexes(); + fail("did not throw"); + } catch (MappingException e) { + assertThat(e.getMessage(), containsString("Found multiple ttl indexed properties!")); + } + } + + @TtlIndex(field = "a",expireAfter = 3600) + public static class TtlIndexTestEntity { + } + + @Test + public void ttlIndex() { + Collection indexes = template.collection(TtlIndexTestEntity.class).getIndexes(); + assertThat(indexes, hasSize(2)); + assertThat(indexes.stream().map(IndexEntity::getType).collect(Collectors.toList()), + hasItems(IndexType.primary, IndexType.ttl)); + IndexEntity ttlIdx = indexes.stream().filter(i -> i.getType() == IndexType.ttl).findFirst().get(); + assertThat(ttlIdx.getFields(), hasSize(1)); + assertThat(ttlIdx.getFields(), hasItems("a")); + assertThat(ttlIdx.getExpireAfter(), is(3600)); + } + }