diff --git a/src/main/java/com/arangodb/springframework/annotation/ArangoSearch.java b/src/main/java/com/arangodb/springframework/annotation/ArangoSearch.java
new file mode 100644
index 000000000..54bc31702
--- /dev/null
+++ b/src/main/java/com/arangodb/springframework/annotation/ArangoSearch.java
@@ -0,0 +1,99 @@
+/*
+ * DISCLAIMER
+ *
+ * Copyright 2018 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.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.arangodb.entity.arangosearch.ConsolidationType;
+import com.arangodb.entity.arangosearch.StoreValuesType;
+
+/**
+ * @author Mark Vollmary
+ *
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE })
+public @interface ArangoSearch {
+
+ /**
+ * @return The name of the arangosearch view
+ */
+ String value() default "";
+
+ /**
+ * @return Wait at least this many milliseconds between committing index data changes and making them visible to
+ * queries (default: 60000, to disable use: 0). For the case where there are a lot of inserts/updates, a
+ * lower value, until commit, will cause the index not to account for them and memory usage would continue
+ * to grow. For the case where there are a few inserts/updates, a higher value will impact performance and
+ * waste disk space for each commit call without any added benefits.
+ */
+ long consolidationIntervalMsec() default -1;
+
+ /**
+ * @return Wait at least this many commits between removing unused files in data directory (default: 10, to disable
+ * use: 0). For the case where the consolidation policies merge segments often (i.e. a lot of
+ * commit+consolidate), a lower value will cause a lot of disk space to be wasted. For the case where the
+ * consolidation policies rarely merge segments (i.e. few inserts/deletes), a higher value will impact
+ * performance without any added benefits.
+ */
+ long cleanupIntervalStep() default -1;
+
+ ConsolidationType consolidationType() default ConsolidationType.BYTES_ACCUM;
+
+ /**
+ * @return Select a given segment for "consolidation" if and only if the formula based on type (as defined above)
+ * evaluates to true, valid value range [0.0, 1.0] (default: 0.85)
+ */
+ double consolidationThreshold() default -1;
+
+ /**
+ * @return Apply the "consolidation" operation if and only if (default: 300): {segmentThreshold} <
+ * number_of_segments
+ */
+ long consolidationSegmentThreshold() default -1;
+
+ /**
+ * @return The list of analyzers to be used for indexing of string values (default: ["identity"]).
+ */
+ String[] analyzers() default {};
+
+ /**
+ * @return The flag determines whether or not to index all fields on a particular level of depth (default: false).
+ */
+ boolean includeAllFields() default false;
+
+ /**
+ * @return The flag determines whether or not values in a lists should be treated separate (default: false).
+ */
+ boolean trackListPositions() default false;
+
+ /**
+ * @return How should the view track the attribute values, this setting allows for additional value retrieval
+ * optimizations (default "none").
+ */
+ StoreValuesType storeValues() default StoreValuesType.NONE;
+
+}
diff --git a/src/main/java/com/arangodb/springframework/annotation/ArangoSearchLinked.java b/src/main/java/com/arangodb/springframework/annotation/ArangoSearchLinked.java
new file mode 100644
index 000000000..137c5aaca
--- /dev/null
+++ b/src/main/java/com/arangodb/springframework/annotation/ArangoSearchLinked.java
@@ -0,0 +1,60 @@
+/*
+ * DISCLAIMER
+ *
+ * Copyright 2018 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;
+
+import com.arangodb.entity.arangosearch.StoreValuesType;
+
+/**
+ * @author Mark Vollmary
+ *
+ */
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.FIELD })
+public @interface ArangoSearchLinked {
+
+ /**
+ * @return The list of analyzers to be used for indexing of string values (default: ["identity"]).
+ */
+ String[] analyzers() default {};
+
+ /**
+ * @return The flag determines whether or not to index all fields on a particular level of depth (default: false).
+ */
+ boolean includeAllFields() default false;
+
+ /**
+ * @return The flag determines whether or not values in a lists should be treated separate (default: false).
+ */
+ boolean trackListPositions() default false;
+
+ /**
+ * @return How should the view track the attribute values, this setting allows for additional value retrieval
+ * optimizations (default "none").
+ */
+ StoreValuesType storeValues() default StoreValuesType.NONE;
+
+}
diff --git a/src/main/java/com/arangodb/springframework/config/ArangoEntityClassScanner.java b/src/main/java/com/arangodb/springframework/config/ArangoEntityClassScanner.java
index 8f5fb0af3..acbdc6a5c 100644
--- a/src/main/java/com/arangodb/springframework/config/ArangoEntityClassScanner.java
+++ b/src/main/java/com/arangodb/springframework/config/ArangoEntityClassScanner.java
@@ -31,6 +31,7 @@
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
+import com.arangodb.springframework.annotation.ArangoSearch;
import com.arangodb.springframework.annotation.Document;
import com.arangodb.springframework.annotation.Edge;
@@ -42,8 +43,9 @@
public class ArangoEntityClassScanner {
@SuppressWarnings("unchecked")
- private static final Class extends Annotation>[] ENTITY_ANNOTATIONS = new Class[] { Document.class, Edge.class };
-
+ private static final Class extends Annotation>[] ENTITY_ANNOTATIONS = new Class[] { Document.class, Edge.class,
+ ArangoSearch.class };
+
@SuppressWarnings("unchecked")
private static final Class extends Annotation>[] ADDITIONAL_ANNOTATIONS = new Class[] { TypeAlias.class };
diff --git a/src/main/java/com/arangodb/springframework/core/ArangoOperations.java b/src/main/java/com/arangodb/springframework/core/ArangoOperations.java
index ebd8b717a..7a0e21b4d 100644
--- a/src/main/java/com/arangodb/springframework/core/ArangoOperations.java
+++ b/src/main/java/com/arangodb/springframework/core/ArangoOperations.java
@@ -38,6 +38,7 @@
import com.arangodb.model.DocumentReadOptions;
import com.arangodb.model.DocumentReplaceOptions;
import com.arangodb.model.DocumentUpdateOptions;
+import com.arangodb.model.arangosearch.ArangoSearchCreateOptions;
import com.arangodb.springframework.core.convert.ArangoConverter;
/**
@@ -561,6 +562,12 @@ public enum UpsertStrategy {
*/
CollectionOperations collection(String name, CollectionCreateOptions options) throws DataAccessException;
+ ArangoSearchOperations arangosearch(Class> entityClass) throws DataAccessException;
+
+ ArangoSearchOperations arangosearch(String name) throws DataAccessException;
+
+ ArangoSearchOperations arangosearch(String name, ArangoSearchCreateOptions options) throws DataAccessException;
+
/**
* Return the operations interface for a user. The user is not created automatically if it does not exists.
*
diff --git a/src/main/java/com/arangodb/springframework/core/ArangoSearchOperations.java b/src/main/java/com/arangodb/springframework/core/ArangoSearchOperations.java
new file mode 100644
index 000000000..130aff0f4
--- /dev/null
+++ b/src/main/java/com/arangodb/springframework/core/ArangoSearchOperations.java
@@ -0,0 +1,84 @@
+/*
+ * DISCLAIMER
+ *
+ * Copyright 2018 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.core;
+
+import org.springframework.dao.DataAccessException;
+
+import com.arangodb.entity.arangosearch.ArangoSearchPropertiesEntity;
+import com.arangodb.model.arangosearch.ArangoSearchPropertiesOptions;
+
+/**
+ * @author Mark Vollmary
+ *
+ */
+public interface ArangoSearchOperations {
+
+ /**
+ * Return the view name
+ *
+ * @return view name
+ */
+ String name();
+
+ /**
+ * Deletes the view from the database.
+ *
+ * @throws DataAccessException
+ */
+ void drop() throws DataAccessException;
+
+ /**
+ * Reads the properties of the specified view.
+ *
+ * @see API
+ * Documentation
+ * @return properties of the view
+ * @throws DataAccessException
+ */
+ ArangoSearchPropertiesEntity getProperties() throws DataAccessException;
+
+ /**
+ * Partially changes properties of the view.
+ *
+ * @see API
+ * Documentation
+ * @param options
+ * properties to change
+ * @return properties of the view
+ * @throws DataAccessException
+ */
+ ArangoSearchPropertiesEntity updateProperties(ArangoSearchPropertiesOptions options) throws DataAccessException;
+
+ /**
+ * Changes properties of the view.
+ *
+ * @see API
+ * Documentation
+ * @param options
+ * properties to change
+ * @return properties of the view
+ * @throws DataAccessException
+ */
+ ArangoSearchPropertiesEntity replaceProperties(ArangoSearchPropertiesOptions options) throws DataAccessException;
+
+}
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..e00aae70c 100644
--- a/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentEntity.java
+++ b/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentEntity.java
@@ -27,7 +27,9 @@
import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.data.mapping.PersistentEntity;
+import com.arangodb.entity.arangosearch.CollectionLink;
import com.arangodb.model.CollectionCreateOptions;
+import com.arangodb.model.arangosearch.ArangoSearchCreateOptions;
import com.arangodb.springframework.annotation.FulltextIndex;
import com.arangodb.springframework.annotation.GeoIndex;
import com.arangodb.springframework.annotation.HashIndex;
@@ -46,6 +48,12 @@ public interface ArangoPersistentEntity
CollectionCreateOptions getCollectionOptions();
+ Optional getArangoSearch();
+
+ Optional getArangoSearchOptions();
+
+ Optional getArangoSearchLinkOptions();
+
Optional getArangoIdProperty();
Optional getRevProperty();
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..599a34ccc 100644
--- a/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentProperty.java
+++ b/src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentProperty.java
@@ -24,6 +24,7 @@
import org.springframework.data.mapping.PersistentProperty;
+import com.arangodb.springframework.annotation.ArangoSearchLinked;
import com.arangodb.springframework.annotation.From;
import com.arangodb.springframework.annotation.FulltextIndexed;
import com.arangodb.springframework.annotation.GeoIndexed;
@@ -64,4 +65,6 @@ public interface ArangoPersistentProperty extends PersistentProperty getFulltextIndexed();
+ Optional getArangoSearchLinked();
+
}
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..369da0ca8 100644
--- a/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentEntity.java
+++ b/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentEntity.java
@@ -48,7 +48,12 @@
import org.springframework.util.StringUtils;
import com.arangodb.entity.CollectionType;
+import com.arangodb.entity.arangosearch.CollectionLink;
+import com.arangodb.entity.arangosearch.ConsolidationPolicy;
+import com.arangodb.entity.arangosearch.FieldLink;
import com.arangodb.model.CollectionCreateOptions;
+import com.arangodb.model.arangosearch.ArangoSearchCreateOptions;
+import com.arangodb.springframework.annotation.ArangoSearch;
import com.arangodb.springframework.annotation.Document;
import com.arangodb.springframework.annotation.Edge;
import com.arangodb.springframework.annotation.FulltextIndex;
@@ -72,8 +77,11 @@ public class DefaultArangoPersistentEntity extends BasicPersistentEntity extends BasicPersistentEntity persistentIndexedProperties;
private final Collection geoIndexedProperties;
private final Collection fulltextIndexedProperties;
+ private final Collection arangosearchLinkedProperties;
private final CollectionCreateOptions collectionOptions;
+ private final Optional arangosearchOptions;
+ private Optional arangosearchLinkOptions;
private final Map, Set extends Annotation>> repeatableAnnotationCache;
public DefaultArangoPersistentEntity(final TypeInformation information) {
super(information);
- collection = StringUtils.uncapitalize(information.getType().getSimpleName());
context = new StandardEvaluationContext();
hashIndexedProperties = new ArrayList<>();
skiplistIndexedProperties = new ArrayList<>();
persistentIndexedProperties = new ArrayList<>();
geoIndexedProperties = new ArrayList<>();
fulltextIndexedProperties = new ArrayList<>();
+ arangosearchLinkedProperties = new ArrayList<>();
repeatableAnnotationCache = new HashMap<>();
final Document document = findAnnotation(Document.class);
final Edge edge = findAnnotation(Edge.class);
+ final ArangoSearch as = findAnnotation(ArangoSearch.class);
+ final String simpleTypeName = StringUtils.uncapitalize(information.getType().getSimpleName());
if (edge != null) {
- collection = StringUtils.hasText(edge.value()) ? edge.value() : collection;
+ collection = StringUtils.hasText(edge.value()) ? edge.value() : simpleTypeName;
collectionOptions = createCollectionOptions(edge);
+ collectionExpression = PARSER.parseExpression(collection, ParserContext.TEMPLATE_EXPRESSION);
} else if (document != null) {
- collection = StringUtils.hasText(document.value()) ? document.value() : collection;
+ collection = StringUtils.hasText(document.value()) ? document.value() : simpleTypeName;
collectionOptions = createCollectionOptions(document);
- } else {
+ collectionExpression = PARSER.parseExpression(collection, ParserContext.TEMPLATE_EXPRESSION);
+ } else if (as == null) {
+ collection = simpleTypeName;
collectionOptions = new CollectionCreateOptions().type(CollectionType.DOCUMENT);
+ collectionExpression = PARSER.parseExpression(collection, ParserContext.TEMPLATE_EXPRESSION);
+ } else {
+ collection = null;
+ collectionOptions = null;
+ collectionExpression = null;
+ }
+ if (as != null) {
+ arangosearch = StringUtils.hasText(as.value()) ? as.value() : simpleTypeName;
+ arangosearchExpression = PARSER.parseExpression(arangosearch, ParserContext.TEMPLATE_EXPRESSION);
+ arangosearchOptions = Optional.ofNullable(createArangoSearchOptions(as));
+ } else {
+ arangosearch = null;
+ arangosearchExpression = null;
+ arangosearchOptions = Optional.empty();
}
- expression = PARSER.parseExpression(collection, ParserContext.TEMPLATE_EXPRESSION);
+ arangosearchLinkOptions = null;
}
private static CollectionCreateOptions createCollectionOptions(final Document annotation) {
@@ -173,7 +203,71 @@ private static CollectionCreateOptions createCollectionOptions(final Edge annota
@Override
public String getCollection() {
- return expression != null ? expression.getValue(context, String.class) : collection;
+ return collectionExpression != null ? collectionExpression.getValue(context, String.class) : collection;
+ }
+
+ private static ArangoSearchCreateOptions createArangoSearchOptions(final ArangoSearch arangosearch) {
+ final ArangoSearchCreateOptions options = new ArangoSearchCreateOptions();
+ final long consolidationIntervalMsec = arangosearch.consolidationIntervalMsec();
+ if (consolidationIntervalMsec > -1) {
+ options.consolidationIntervalMsec(consolidationIntervalMsec);
+ }
+ final long cleanupIntervalStep = arangosearch.cleanupIntervalStep();
+ if (cleanupIntervalStep > -1) {
+ options.cleanupIntervalStep(cleanupIntervalStep);
+ }
+ final double consolidationThreshold = arangosearch.consolidationThreshold();
+ final long consolidationSegmentThreshold = arangosearch.consolidationSegmentThreshold();
+ if (consolidationThreshold > -1 || consolidationSegmentThreshold > -1) {
+ final ConsolidationPolicy consolidationPolicy = ConsolidationPolicy.of(arangosearch.consolidationType());
+ options.consolidationPolicy(consolidationPolicy);
+ if (consolidationThreshold > -1) {
+ consolidationPolicy.threshold(consolidationThreshold);
+ }
+ if (consolidationSegmentThreshold > -1) {
+ consolidationPolicy.segmentThreshold(consolidationSegmentThreshold);
+ }
+ }
+ return options;
+ }
+
+ private CollectionLink createArangosearchLinkOptions() {
+ final String collection = getCollection();
+ if (collection == null) {
+ return null;
+ }
+ final ArangoSearch as = findAnnotation(ArangoSearch.class);
+ if (as == null) {
+ return null;
+ }
+ final CollectionLink options = CollectionLink.on(collection);
+ final String[] analyzers = as.analyzers();
+ if (analyzers.length > 0) {
+ options.analyzers(analyzers);
+ }
+ options.includeAllFields(as.includeAllFields());
+ options.trackListPositions(as.trackListPositions());
+ options.storeValues(as.storeValues());
+ arangosearchLinkedProperties.stream().forEach(property -> {
+ final FieldLink fieldLink = FieldLink.on(property.getFieldName());
+ property.getArangoSearchLinked().ifPresent(link -> {
+ final String[] linkAnalyzers = link.analyzers();
+ if (linkAnalyzers.length > 0) {
+ fieldLink.analyzers(linkAnalyzers);
+ }
+ fieldLink.includeAllFields(link.includeAllFields());
+ fieldLink.trackListPositions(link.trackListPositions());
+ fieldLink.storeValues(link.storeValues());
+ });
+ options.fields(fieldLink);
+ });
+ return options;
+ }
+
+ @Override
+ public Optional getArangoSearch() {
+ return Optional.ofNullable(
+ arangosearchExpression != null ? arangosearchExpression.getValue(context, String.class) : arangosearch);
}
@Override
@@ -197,6 +291,7 @@ public void addPersistentProperty(final ArangoPersistentProperty property) {
property.getPersistentIndexed().ifPresent(i -> persistentIndexedProperties.add(property));
property.getGeoIndexed().ifPresent(i -> geoIndexedProperties.add(property));
property.getFulltextIndexed().ifPresent(i -> fulltextIndexedProperties.add(property));
+ property.getArangoSearchLinked().ifPresent(i -> arangosearchLinkedProperties.add(property));
}
@Override
@@ -214,6 +309,19 @@ public CollectionCreateOptions getCollectionOptions() {
return collectionOptions;
}
+ @Override
+ public Optional getArangoSearchOptions() {
+ return arangosearchOptions;
+ }
+
+ @Override
+ public Optional getArangoSearchLinkOptions() {
+ if (arangosearchLinkOptions == null) {
+ arangosearchLinkOptions = Optional.ofNullable(createArangosearchLinkOptions());
+ }
+ return arangosearchLinkOptions;
+ }
+
@Override
public Collection getHashIndexes() {
final Collection indexes = getIndexes(HashIndex.class);
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..40ed1ce6e 100644
--- a/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentProperty.java
+++ b/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentProperty.java
@@ -32,6 +32,7 @@
import org.springframework.util.StringUtils;
import com.arangodb.springframework.annotation.ArangoId;
+import com.arangodb.springframework.annotation.ArangoSearchLinked;
import com.arangodb.springframework.annotation.Field;
import com.arangodb.springframework.annotation.From;
import com.arangodb.springframework.annotation.FulltextIndexed;
@@ -145,4 +146,9 @@ public Optional getFulltextIndexed() {
return Optional.ofNullable(findAnnotation(FulltextIndexed.class));
}
+ @Override
+ public Optional getArangoSearchLinked() {
+ return Optional.ofNullable(findAnnotation(ArangoSearchLinked.class));
+ }
+
}
diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoSearchCacheValue.java b/src/main/java/com/arangodb/springframework/core/template/ArangoSearchCacheValue.java
new file mode 100644
index 000000000..51854efb7
--- /dev/null
+++ b/src/main/java/com/arangodb/springframework/core/template/ArangoSearchCacheValue.java
@@ -0,0 +1,31 @@
+package com.arangodb.springframework.core.template;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import com.arangodb.ArangoSearch;
+
+class ArangoSearchCacheValue {
+
+ private final ArangoSearch view;
+ private final Collection> entities;
+
+ public ArangoSearchCacheValue(final ArangoSearch view) {
+ super();
+ this.view = view;
+ this.entities = new ArrayList<>();
+ }
+
+ public ArangoSearch getView() {
+ return view;
+ }
+
+ public Collection> getEntities() {
+ return entities;
+ }
+
+ public void addEntityClass(final Class> entityClass) {
+ entities.add(entityClass);
+ }
+
+}
\ No newline at end of file
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 69786e808..30d73b4dc 100644
--- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java
+++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java
@@ -53,6 +53,7 @@
import com.arangodb.ArangoDB;
import com.arangodb.ArangoDBException;
import com.arangodb.ArangoDatabase;
+import com.arangodb.ArangoSearch;
import com.arangodb.entity.ArangoDBVersion;
import com.arangodb.entity.DocumentEntity;
import com.arangodb.entity.MultiDocumentEntity;
@@ -69,12 +70,15 @@
import com.arangodb.model.HashIndexOptions;
import com.arangodb.model.PersistentIndexOptions;
import com.arangodb.model.SkiplistIndexOptions;
+import com.arangodb.model.arangosearch.ArangoSearchCreateOptions;
+import com.arangodb.model.arangosearch.ArangoSearchPropertiesOptions;
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.core.ArangoOperations;
+import com.arangodb.springframework.core.ArangoSearchOperations;
import com.arangodb.springframework.core.CollectionOperations;
import com.arangodb.springframework.core.UserOperations;
import com.arangodb.springframework.core.convert.ArangoConverter;
@@ -109,6 +113,7 @@ public class ArangoTemplate implements ArangoOperations, CollectionCallback, App
private final Expression databaseExpression;
private final Map databaseCache;
private final Map collectionCache;
+ private final Map arangosearchCache;
private final StandardEvaluationContext context;
@@ -133,6 +138,7 @@ public ArangoTemplate(final ArangoDB arango, final String database, final Arango
this.context = new StandardEvaluationContext();
// set concurrency level to 1 as writes are very rare compared to reads
collectionCache = new ConcurrentHashMap<>(8, 0.9f, 1);
+ arangosearchCache = new ConcurrentHashMap<>(8, 0.9f, 1);
databaseCache = new ConcurrentHashMap<>(8, 0.9f, 1);
version = null;
}
@@ -188,6 +194,10 @@ private ArangoCollection _collection(
if (persistentEntity != null && !entities.contains(entityClass)) {
value.addEntityClass(entityClass);
ensureCollectionIndexes(collection(collection), persistentEntity);
+ persistentEntity.getArangoSearch().ifPresent(arangosearch -> {
+ // also create arangosearch view if not already created
+ _arangosearch(arangosearch, persistentEntity, persistentEntity.getArangoSearchOptions().orElse(null));
+ });
}
return collection;
}
@@ -743,6 +753,8 @@ public void dropDatabase() throws DataAccessException {
databaseCache.remove(db.name());
collectionCache.keySet().stream().filter(key -> key.getDb().equals(db.name()))
.forEach(key -> collectionCache.remove(key));
+ arangosearchCache.keySet().stream().filter(key -> key.getDb().equals(db.name()))
+ .forEach(key -> arangosearchCache.remove(key));
}
@Override
@@ -765,6 +777,72 @@ private CollectionOperations collection(final ArangoCollection collection) {
return new DefaultCollectionOperations(collection, collectionCache, exceptionTranslator);
}
+ @Override
+ public ArangoSearchOperations arangosearch(final Class> entityClass) throws DataAccessException {
+ return arangosearch(_arangosearch(entityClass));
+ }
+
+ @Override
+ public ArangoSearchOperations arangosearch(final String name) throws DataAccessException {
+ return arangosearch(_arangosearch(name, null, null));
+ }
+
+ @Override
+ public ArangoSearchOperations arangosearch(final String name, final ArangoSearchCreateOptions options)
+ throws DataAccessException {
+ return arangosearch(_arangosearch(name, null, options));
+ }
+
+ private ArangoSearchOperations arangosearch(final ArangoSearch view) {
+ return new DefaultArangoSearchOperations(view, arangosearchCache, exceptionTranslator);
+ }
+
+ private ArangoSearch _arangosearch(final Class> entityClass) {
+ final ArangoPersistentEntity> persistentEntity = converter.getMappingContext()
+ .getRequiredPersistentEntity(entityClass);
+ return persistentEntity.getArangoSearch().map(arangosearch -> {
+ return _arangosearch(arangosearch, persistentEntity,
+ persistentEntity.getArangoSearchOptions().orElse(null));
+ }).orElse(null);
+
+ }
+
+ private ArangoSearch _arangosearch(
+ final String name,
+ final ArangoPersistentEntity> persistentEntity,
+ final ArangoSearchCreateOptions options) {
+
+ final ArangoDatabase db = db();
+ final Class> entityClass = persistentEntity != null ? persistentEntity.getType() : null;
+ final ArangoSearchCacheValue value = arangosearchCache.computeIfAbsent(new CollectionCacheKey(db.name(), name),
+ key -> {
+ final ArangoSearch view = db.arangoSearch(name);
+ if (!view.exists()) {
+ view.create(options);
+ }
+ return new ArangoSearchCacheValue(view);
+ });
+ final Collection> entities = value.getEntities();
+ final ArangoSearch view = value.getView();
+ if (persistentEntity != null && !entities.contains(entityClass)) {
+ value.addEntityClass(entityClass);
+ ensureArangoSearchLinks(arangosearch(view), persistentEntity);
+ if (persistentEntity.getCollection() != null) {
+ // also create collection if not already created
+ _collection(persistentEntity.getCollection(), persistentEntity,
+ persistentEntity.getCollectionOptions());
+ }
+ }
+ return view;
+ }
+
+ private void ensureArangoSearchLinks(
+ final ArangoSearchOperations arangosearch,
+ final ArangoPersistentEntity> persistentEntity) {
+ persistentEntity.getArangoSearchLinkOptions().ifPresent(
+ linkOptions -> arangosearch.updateProperties(new ArangoSearchPropertiesOptions().link(linkOptions)));
+ }
+
@Override
public UserOperations user(final String username) {
return new DefaultUserOperation(db(), username, exceptionTranslator, this);
diff --git a/src/main/java/com/arangodb/springframework/core/template/DefaultArangoSearchOperations.java b/src/main/java/com/arangodb/springframework/core/template/DefaultArangoSearchOperations.java
new file mode 100644
index 000000000..3301b3e97
--- /dev/null
+++ b/src/main/java/com/arangodb/springframework/core/template/DefaultArangoSearchOperations.java
@@ -0,0 +1,100 @@
+/*
+ * DISCLAIMER
+ *
+ * Copyright 2018 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.core.template;
+
+import java.util.Map;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.dao.support.PersistenceExceptionTranslator;
+
+import com.arangodb.ArangoDBException;
+import com.arangodb.ArangoSearch;
+import com.arangodb.entity.arangosearch.ArangoSearchPropertiesEntity;
+import com.arangodb.model.arangosearch.ArangoSearchPropertiesOptions;
+import com.arangodb.springframework.core.ArangoSearchOperations;
+
+/**
+ * @author Mark Vollmary
+ *
+ */
+public class DefaultArangoSearchOperations implements ArangoSearchOperations {
+
+ private final ArangoSearch view;
+ private final Map viewCache;
+ private final PersistenceExceptionTranslator exceptionTranslator;
+
+ protected DefaultArangoSearchOperations(final ArangoSearch view,
+ final Map viewCache,
+ final PersistenceExceptionTranslator exceptionTranslator) {
+ this.view = view;
+ this.viewCache = viewCache;
+ this.exceptionTranslator = exceptionTranslator;
+ }
+
+ private DataAccessException translateExceptionIfPossible(final RuntimeException exception) {
+ return exceptionTranslator.translateExceptionIfPossible(exception);
+ }
+
+ @Override
+ public String name() {
+ return view.name();
+ }
+
+ @Override
+ public void drop() throws DataAccessException {
+ viewCache.remove(new CollectionCacheKey(view.db().name(), view.name()));
+ try {
+ view.drop();
+ } catch (final ArangoDBException e) {
+ throw translateExceptionIfPossible(e);
+ }
+ }
+
+ @Override
+ public ArangoSearchPropertiesEntity getProperties() throws DataAccessException {
+ try {
+ return view.getProperties();
+ } catch (final ArangoDBException e) {
+ throw translateExceptionIfPossible(e);
+ }
+ }
+
+ @Override
+ public ArangoSearchPropertiesEntity updateProperties(final ArangoSearchPropertiesOptions options)
+ throws DataAccessException {
+ try {
+ return view.updateProperties(options);
+ } catch (final ArangoDBException e) {
+ throw translateExceptionIfPossible(e);
+ }
+ }
+
+ @Override
+ public ArangoSearchPropertiesEntity replaceProperties(final ArangoSearchPropertiesOptions options)
+ throws DataAccessException {
+ try {
+ return view.replaceProperties(options);
+ } catch (final ArangoDBException e) {
+ throw translateExceptionIfPossible(e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java b/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java
index 379f2142a..b99b559f3 100644
--- a/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java
+++ b/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java
@@ -21,6 +21,8 @@
package com.arangodb.springframework.repository;
import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
import org.springframework.data.mapping.context.MappingContext;
@@ -67,23 +69,42 @@ public ArangoRepositoryFactory(final ArangoOperations arangoOperations) {
@SuppressWarnings("unchecked")
@Override
public ArangoEntityInformation getEntityInformation(final Class domainClass) {
- return new ArangoPersistentEntityInformation(
+ return new ArangoPersistentEntityInformation<>(
(ArangoPersistentEntity) context.getRequiredPersistentEntity(domainClass));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
protected Object getTargetRepository(final RepositoryInformation metadata) {
- return new SimpleArangoRepository(arangoOperations, metadata.getDomainType());
+ final Class> repositoryBaseClass = metadata.getRepositoryBaseClass();
+ final Repository repository;
+ if (repositoryBaseClass == SimpleArangoRepository.class) {
+ repository = new SimpleArangoRepository(arangoOperations, metadata.getDomainType());
+ } else if (repositoryBaseClass == SimpleArangoSearchRepository.class) {
+ repository = new SimpleArangoSearchRepository(arangoOperations, metadata.getDomainType());
+ } else {
+ repository = null;
+ }
+ return repository;
}
@Override
protected Class> getRepositoryBaseClass(final RepositoryMetadata metadata) {
- return SimpleArangoRepository.class;
+ final Class> repositoryInterface = metadata.getRepositoryInterface();
+ final List> interfaces = Arrays.asList(repositoryInterface.getInterfaces());
+ final Class> repositoryBaseClass;
+ if (interfaces.contains(ArangoRepository.class)) {
+ repositoryBaseClass = SimpleArangoRepository.class;
+ } else if (interfaces.contains(ArangoSearchRepository.class)) {
+ repositoryBaseClass = SimpleArangoSearchRepository.class;
+ } else {
+ repositoryBaseClass = null;
+ }
+ return repositoryBaseClass;
}
@Override
- protected RepositoryMetadata getRepositoryMetadata(Class> repositoryInterface) {
+ protected RepositoryMetadata getRepositoryMetadata(final Class> repositoryInterface) {
Assert.notNull(repositoryInterface, "Repository interface must not be null!");
return Repository.class.isAssignableFrom(repositoryInterface)
@@ -143,13 +164,13 @@ static class DefaultArangoRepositoryMetadata extends DefaultRepositoryMetadata {
private final TypeInformation> typeInformation;
- public DefaultArangoRepositoryMetadata(Class> repositoryInterface) {
+ public DefaultArangoRepositoryMetadata(final Class> repositoryInterface) {
super(repositoryInterface);
typeInformation = ClassTypeInformation.from(repositoryInterface);
}
@Override
- public Class> getReturnedDomainClass(Method method) {
+ public Class> getReturnedDomainClass(final Method method) {
if (ArangoCursor.class.isAssignableFrom(method.getReturnType())) {
return typeInformation.getReturnType(method).getRequiredComponentType().getType();
} else {
@@ -163,13 +184,13 @@ static class AnnotationArangoRepositoryMetadata extends AnnotationRepositoryMeta
private final TypeInformation> typeInformation;
- public AnnotationArangoRepositoryMetadata(Class> repositoryInterface) {
+ public AnnotationArangoRepositoryMetadata(final Class> repositoryInterface) {
super(repositoryInterface);
typeInformation = ClassTypeInformation.from(repositoryInterface);
}
@Override
- public Class> getReturnedDomainClass(Method method) {
+ public Class> getReturnedDomainClass(final Method method) {
if (ArangoCursor.class.isAssignableFrom(method.getReturnType())) {
return typeInformation.getReturnType(method).getRequiredComponentType().getType();
} else {
diff --git a/src/main/java/com/arangodb/springframework/repository/ArangoSearchRepository.java b/src/main/java/com/arangodb/springframework/repository/ArangoSearchRepository.java
new file mode 100644
index 000000000..09eb692aa
--- /dev/null
+++ b/src/main/java/com/arangodb/springframework/repository/ArangoSearchRepository.java
@@ -0,0 +1,99 @@
+/*
+ * DISCLAIMER
+ *
+ * Copyright 2018 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.repository;
+
+import java.util.Optional;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.repository.NoRepositoryBean;
+import org.springframework.data.repository.Repository;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+
+/**
+ * @author Mark Vollmary
+ *
+ */
+@NoRepositoryBean
+public interface ArangoSearchRepository extends QueryByExampleExecutor, Repository {
+
+ /**
+ * Retrieves an entity by its id.
+ *
+ * @param id
+ * must not be {@literal null}.
+ * @return the entity with the given id or {@literal Optional#empty()} if none found
+ * @throws IllegalArgumentException
+ * if {@code id} is {@literal null}.
+ */
+ Optional findById(String id);
+
+ /**
+ * Returns whether an entity with the given id exists.
+ *
+ * @param id
+ * must not be {@literal null}.
+ * @return {@literal true} if an entity with the given id exists, {@literal false} otherwise.
+ * @throws IllegalArgumentException
+ * if {@code id} is {@literal null}.
+ */
+ boolean existsById(String id);
+
+ /**
+ * Returns all instances of the type.
+ *
+ * @return all entities
+ */
+ Iterable findAll();
+
+ /**
+ * Returns all instances of the type with the given IDs.
+ *
+ * @param ids
+ * @return
+ */
+ Iterable findAllById(Iterable ids);
+
+ /**
+ * Returns the number of entities available.
+ *
+ * @return the number of entities
+ */
+ long count();
+
+ /**
+ * Returns all entities sorted by the given options.
+ *
+ * @param sort
+ * @return all entities sorted by the given options
+ */
+ Iterable findAll(Sort sort);
+
+ /**
+ * Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.
+ *
+ * @param pageable
+ * @return a page of entities
+ */
+ Page findAll(Pageable pageable);
+
+}
diff --git a/src/main/java/com/arangodb/springframework/repository/SimpleArangoSearchRepository.java b/src/main/java/com/arangodb/springframework/repository/SimpleArangoSearchRepository.java
new file mode 100644
index 000000000..57259f5d0
--- /dev/null
+++ b/src/main/java/com/arangodb/springframework/repository/SimpleArangoSearchRepository.java
@@ -0,0 +1,189 @@
+/*
+ * DISCLAIMER
+ *
+ * Copyright 2018 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.repository;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.lang.Nullable;
+import org.springframework.stereotype.Repository;
+
+import com.arangodb.ArangoCursor;
+import com.arangodb.model.AqlQueryOptions;
+import com.arangodb.springframework.core.ArangoOperations;
+import com.arangodb.springframework.core.mapping.ArangoMappingContext;
+import com.arangodb.springframework.core.util.AqlUtils;
+import com.arangodb.util.MapBuilder;
+
+/**
+ * @author Mark Vollmary
+ *
+ */
+@Repository
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class SimpleArangoSearchRepository implements ArangoSearchRepository {
+
+ private final ArangoExampleConverter exampleConverter;
+ private final ArangoOperations arangoOperations;
+ private final Class domainClass;
+
+ public SimpleArangoSearchRepository(final ArangoOperations arangoOperations, final Class domainClass) {
+ super();
+ this.arangoOperations = arangoOperations;
+ this.domainClass = domainClass;
+ this.exampleConverter = new ArangoExampleConverter(
+ (ArangoMappingContext) arangoOperations.getConverter().getMappingContext());
+ }
+
+ @Override
+ public Optional findById(final String id) {
+ return Optional.ofNullable(arangoOperations.query("FOR e IN @@view FILTER e._id == @id LIMIT 1 RETURN e",
+ new MapBuilder().put("@view", getViewName()).put("id", id).get(), domainClass).first());
+ }
+
+ @Override
+ public boolean existsById(final String id) {
+ return arangoOperations
+ .query("FOR e IN @@view FILTER e._id == @id LIMIT 1 RETURN true",
+ new MapBuilder().put("@view", getViewName()).put("id", id).get(), Boolean.class)
+ .first() == Boolean.TRUE;
+ }
+
+ @Override
+ public Iterable findAll() {
+ return arangoOperations.query("FOR e IN @@view RETURN e", new MapBuilder().put("@view", getViewName()).get(),
+ domainClass);
+ }
+
+ @Override
+ public Iterable findAllById(final Iterable ids) {
+ return arangoOperations.query("FOR e IN @@view FILTER e._id IN @ids RETURN e",
+ new MapBuilder().put("@view", getViewName()).put("ids", ids).get(), domainClass);
+ }
+
+ @Override
+ public long count() {
+ final Map bindVars = new MapBuilder().put("@view", getViewName()).get();
+ final String query = "FOR e IN @@view COLLECT WITH COUNT INTO length RETURN length";
+ return arangoOperations.query(query, bindVars, Long.class).first();
+ }
+
+ @Override
+ public Iterable findAll(final Sort sort) {
+ return _findAll(sort, null);
+ }
+
+ @Override
+ public Page findAll(final Pageable pageable) {
+ final ArangoCursor cursor = _findAll(pageable, null);
+ return new PageImpl<>(cursor.asListRemaining(), pageable, cursor.getStats().getFullCount());
+ }
+
+ @Override
+ public Optional findOne(final Example example) {
+ final Map bindVars = new MapBuilder().put("@view", getViewName()).get();
+ final String query = "FOR e IN @@view " + buildSearchClause(example, bindVars) + " LIMIT 1 RETURN e";
+ return Optional.ofNullable((S) arangoOperations.query(query, bindVars, domainClass).first());
+ }
+
+ @Override
+ public Iterable findAll(final Example example) {
+ return _findAll((Pageable) null, example);
+ }
+
+ @Override
+ public Iterable findAll(final Example example, final Sort sort) {
+ return _findAll(sort, example);
+ }
+
+ @Override
+ public Page findAll(final Example example, final Pageable pageable) {
+ final ArangoCursor cursor = _findAll(pageable, example);
+ return new PageImpl<>(cursor.asListRemaining(), pageable, cursor.getStats().getFullCount());
+ }
+
+ @Override
+ public long count(final Example example) {
+ final Map bindVars = new MapBuilder().put("@view", getViewName()).get();
+ final String query = "FOR e IN @@view " + buildSearchClause(example, bindVars)
+ + " COLLECT WITH COUNT INTO length RETURN length";
+ return arangoOperations.query(query, bindVars, Long.class).first();
+ }
+
+ @Override
+ public boolean exists(final Example example) {
+ final Map bindVars = new MapBuilder().put("@view", getViewName()).get();
+ final String query = "FOR e IN @@view " + buildSearchClause(example, bindVars) + " LIMIT 1 RETURN true";
+ return arangoOperations.query(query, bindVars, Boolean.class).first() == Boolean.TRUE;
+ }
+
+ private ArangoCursor _findAll(final Sort sort, @Nullable final Example example) {
+ final Map bindVars = new HashMap<>();
+ bindVars.put("@view", getViewName());
+
+ final String query = "FOR e IN @@view " + buildSearchClause(example, bindVars) + " "
+ + buildSortClause(sort, "e") + " RETURN e";
+ final ArangoCursor cursor = arangoOperations.query(query, bindVars, domainClass);
+ return cursor;
+ }
+
+ private ArangoCursor _findAll(final Pageable pageable, @Nullable final Example example) {
+ final Map bindVars = new HashMap<>();
+ bindVars.put("@view", getViewName());
+
+ final String query = "FOR e IN @@view " + buildSearchClause(example, bindVars) + " "
+ + buildPageableClause(pageable, "e") + " RETURN e";
+ final AqlQueryOptions options = new AqlQueryOptions();
+ if (pageable != null && pageable.isPaged()) {
+ options.fullCount(true);
+ }
+ final ArangoCursor cursor = arangoOperations.query(query, bindVars, options, domainClass);
+ return cursor;
+ }
+
+ private String buildSearchClause(final Example example, final Map bindVars) {
+ if (example == null) {
+ return "";
+ }
+
+ final String predicate = exampleConverter.convertExampleToPredicate(example, bindVars);
+ return predicate == null ? "" : "FILTER " + predicate;
+ }
+
+ private String buildPageableClause(final Pageable pageable, final String varName) {
+ return pageable == null ? "" : AqlUtils.buildPageableClause(pageable, varName);
+ }
+
+ private String buildSortClause(final Sort sort, final String varName) {
+ return sort == null ? "" : AqlUtils.buildSortClause(sort, varName);
+ }
+
+ private String getViewName() {
+ return arangoOperations.getConverter().getMappingContext().getPersistentEntity(domainClass).getArangoSearch()
+ .get();
+ }
+}
diff --git a/src/test/java/com/arangodb/springframework/repository/ArangoSearchRepositoryTest.java b/src/test/java/com/arangodb/springframework/repository/ArangoSearchRepositoryTest.java
new file mode 100644
index 000000000..8b76b4b66
--- /dev/null
+++ b/src/test/java/com/arangodb/springframework/repository/ArangoSearchRepositoryTest.java
@@ -0,0 +1,193 @@
+/*
+ * DISCLAIMER
+ *
+ * Copyright 2018 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.repository;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+
+import com.arangodb.springframework.AbstractArangoTest;
+import com.arangodb.springframework.core.ArangoOperations;
+import com.arangodb.springframework.testdata.ArangoSearchTestEntity;
+
+/**
+ * @author Mark Vollmary
+ *
+ */
+
+public class ArangoSearchRepositoryTest extends AbstractArangoTest {
+
+ @Autowired
+ ArangoOperations operations;
+
+ @Autowired
+ ArangoSearchTestRepo repository;
+
+ public ArangoSearchRepositoryTest() {
+ super();
+ }
+
+ @Test
+ public void findById() throws InterruptedException {
+ final ArangoSearchTestEntity entity = new ArangoSearchTestEntity("test");
+ operations.insert(entity);
+ Thread.sleep(1000);
+ final Optional find = repository.findById(entity.getId());
+ assertThat(find.isPresent(), is(true));
+ assertThat(find.get().getValue(), is("test"));
+ }
+
+ @Test
+ public void query() throws InterruptedException {
+ final ArangoSearchTestEntity entity = new ArangoSearchTestEntity("test");
+ operations.insert(entity);
+ Thread.sleep(1000);
+ final List find = repository.findByValue("test");
+ assertThat(find.size(), is(1));
+ assertThat(find.get(0).getValue(), is("test"));
+ }
+
+ @Test
+ public void findAll() throws InterruptedException {
+ final ArangoSearchTestEntity entity = new ArangoSearchTestEntity("test");
+ operations.insert(entity);
+ Thread.sleep(1000);
+ final Iterable find = repository.findAll();
+ final Iterator iterator = find.iterator();
+ assertThat(iterator.next().getValue(), is("test"));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void findAllById() throws InterruptedException {
+ final ArangoSearchTestEntity entity = new ArangoSearchTestEntity("test");
+ operations.insert(entity);
+ Thread.sleep(1000);
+ final Iterable find = repository.findAllById(Arrays.asList(entity.getId()));
+ final Iterator iterator = find.iterator();
+ assertThat(iterator.next().getValue(), is("test"));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void count() throws InterruptedException {
+ final ArangoSearchTestEntity entity = new ArangoSearchTestEntity("test");
+ operations.insert(entity);
+ Thread.sleep(1000);
+ final long count = repository.count();
+ assertThat(count, is(1L));
+ }
+
+ @Test
+ public void findAllWithSort() throws InterruptedException {
+ operations.insert(Arrays.asList(new ArangoSearchTestEntity("test2"), new ArangoSearchTestEntity("test1")),
+ ArangoSearchTestEntity.class);
+ Thread.sleep(1000);
+ final Iterable find = repository.findAll(Sort.by("value"));
+ final Iterator iterator = find.iterator();
+ assertThat(iterator.next().getValue(), is("test1"));
+ assertThat(iterator.hasNext(), is(true));
+ assertThat(iterator.next().getValue(), is("test2"));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void findAllWithPageable() throws InterruptedException {
+ operations.insert(Arrays.asList(new ArangoSearchTestEntity("test2"), new ArangoSearchTestEntity("test1")),
+ ArangoSearchTestEntity.class);
+ Thread.sleep(1000);
+ final Page find = repository.findAll(PageRequest.of(1, 1, Sort.by("value")));
+ assertThat(find.getContent().size(), is(1));
+ assertThat(find.getContent().iterator().next().getValue(), is("test2"));
+ }
+
+ @Test
+ public void findOneByExample() throws InterruptedException {
+ final ArangoSearchTestEntity entity = new ArangoSearchTestEntity("test");
+ operations.insert(entity);
+ Thread.sleep(1000);
+ final Optional find = repository.findOne(Example.of(entity));
+ assertThat(find.isPresent(), is(true));
+ assertThat(find.get().getValue(), is("test"));
+ }
+
+ @Test
+ public void findAllByExample() throws InterruptedException {
+ final ArangoSearchTestEntity entity = new ArangoSearchTestEntity("test");
+ operations.insert(entity);
+ Thread.sleep(1000);
+ final Iterable find = repository.findAll(Example.of(entity));
+ final Iterator iterator = find.iterator();
+ assertThat(iterator.next().getValue(), is("test"));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void findAllByExampleWithSort() throws InterruptedException {
+ final ArangoSearchTestEntity entity = new ArangoSearchTestEntity("test");
+ operations.insert(entity);
+ Thread.sleep(1000);
+ final Iterable find = repository.findAll(Example.of(entity), Sort.by("value"));
+ final Iterator iterator = find.iterator();
+ assertThat(iterator.next().getValue(), is("test"));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void findAllByExampleWithPageable() throws InterruptedException {
+ final ArangoSearchTestEntity entity = new ArangoSearchTestEntity("test");
+ operations.insert(entity);
+ Thread.sleep(1000);
+ final Iterable find = repository.findAll(Example.of(entity), PageRequest.of(0, 1));
+ final Iterator iterator = find.iterator();
+ assertThat(iterator.next().getValue(), is("test"));
+ assertThat(iterator.hasNext(), is(false));
+ }
+
+ @Test
+ public void countByExample() throws InterruptedException {
+ final ArangoSearchTestEntity entity = new ArangoSearchTestEntity("test");
+ operations.insert(entity);
+ Thread.sleep(1000);
+ final long count = repository.count(Example.of(entity));
+ assertThat(count, is(1L));
+ }
+
+ @Test
+ public void existsByExample() throws InterruptedException {
+ final ArangoSearchTestEntity entity = new ArangoSearchTestEntity("test");
+ operations.insert(entity);
+ Thread.sleep(1000);
+ final boolean exists = repository.exists(Example.of(entity));
+ assertThat(exists, is(true));
+ }
+}
diff --git a/src/test/java/com/arangodb/springframework/repository/ArangoSearchTestRepo.java b/src/test/java/com/arangodb/springframework/repository/ArangoSearchTestRepo.java
new file mode 100644
index 000000000..033c00fa8
--- /dev/null
+++ b/src/test/java/com/arangodb/springframework/repository/ArangoSearchTestRepo.java
@@ -0,0 +1,39 @@
+/*
+ * DISCLAIMER
+ *
+ * Copyright 2018 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.repository;
+
+import java.util.List;
+
+import org.springframework.data.repository.query.Param;
+
+import com.arangodb.springframework.annotation.Query;
+import com.arangodb.springframework.testdata.ArangoSearchTestEntity;
+
+/**
+ * @author Mark Vollmary
+ *
+ */
+public interface ArangoSearchTestRepo extends ArangoSearchRepository {
+
+ @Query("FOR i IN ArangoSearchTestEntityView SEARCH i.value == @value RETURN i")
+ List findByValue(@Param("value") String value);
+
+}
diff --git a/src/test/java/com/arangodb/springframework/testdata/ArangoSearchTestEntity.java b/src/test/java/com/arangodb/springframework/testdata/ArangoSearchTestEntity.java
new file mode 100644
index 000000000..e19564316
--- /dev/null
+++ b/src/test/java/com/arangodb/springframework/testdata/ArangoSearchTestEntity.java
@@ -0,0 +1,66 @@
+/*
+ * DISCLAIMER
+ *
+ * Copyright 2018 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.testdata;
+
+import com.arangodb.springframework.annotation.ArangoId;
+import com.arangodb.springframework.annotation.ArangoSearch;
+import com.arangodb.springframework.annotation.ArangoSearchLinked;
+import com.arangodb.springframework.annotation.Document;
+
+/**
+ * @author Mark Vollmary
+ *
+ */
+@ArangoSearch("ArangoSearchTestEntityView")
+@Document
+public class ArangoSearchTestEntity {
+
+ @ArangoId
+ private String id;
+ @ArangoSearchLinked
+ private String value;
+
+ public ArangoSearchTestEntity() {
+ super();
+ }
+
+ public ArangoSearchTestEntity(final String value) {
+ super();
+ this.value = value;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(final String value) {
+ this.value = value;
+ }
+
+}