From ff127b2038dafce90c68e88eb34dbf2e5330232b Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 14 Dec 2017 08:38:11 +0100 Subject: [PATCH 01/94] prepare release 1.0.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 38720f521..e092632b8 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.0.0-SNAPSHOT + 1.0.0 2017 jar @@ -38,7 +38,7 @@ mpv1989 - Mark + Mark Vollmary https://github.com/mpv1989 From 73311db2498883d1e16c2cee3296d0e072ed6a1d Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 14 Dec 2017 08:41:31 +0100 Subject: [PATCH 02/94] Fix pom --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index e092632b8..a9d314b24 100644 --- a/pom.xml +++ b/pom.xml @@ -10,6 +10,7 @@ arangodb-spring-data ArangoDB Spring Data + http://maven.apache.org From bb03b73d8cb11657bda23fe2930acc4cdd19d1d4 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 14 Dec 2017 08:48:29 +0100 Subject: [PATCH 03/94] snapshot version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a9d314b24..5574d6e12 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.0.0 + 1.0.1-SNAPSHOT 2017 jar From 0e82275640e7b4bb8f94fb34f88e0cf30bc46f5c Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 26 Jan 2018 10:06:43 +0100 Subject: [PATCH 04/94] Fix issue #8 missing WITH information in AQL when resolving annotation @Relations --- ChangeLog | 3 +++ .../convert/resolver/RelationsResolver.java | 22 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 ChangeLog diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 000000000..037eca990 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,3 @@ +v1.0.1 (2018-01-26) +--------------------------- +* fixed missing WITH information in AQL when resolving annotation @Relations (Issue #8) diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java index 551f2df1c..35877c360 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java @@ -55,18 +55,16 @@ public Object resolveMultiple(final String id, final Class type, final Relati @Override public Object resolve(final String id, final Class type, final Relations annotation) { - return template - .query( - "WITH @@edges FOR v IN " + Math.max(1, annotation.minDepth()) + ".." - + Math.max(1, annotation.maxDepth()) + " " + annotation.direction() - + " @start @@edges OPTIONS {bfs: true, uniqueVertices: \"global\"} RETURN v", - new MapBuilder().put("start", id) - .put("@edges", - Arrays.asList(annotation.edges()).stream().map((e) -> template.collection(e).name()) - .reduce((a, b) -> a + ", " + b).get()) - .get(), - new AqlQueryOptions(), type) - .asListRemaining(); + return template.query( + "WITH @@vertex FOR v IN " + Math.max(1, annotation.minDepth()) + ".." + Math.max(1, annotation.maxDepth()) + + " " + annotation.direction() + + " @start @@edges OPTIONS {bfs: true, uniqueVertices: \"global\"} RETURN v", + new MapBuilder().put("start", id) + .put("@edges", + Arrays.asList(annotation.edges()).stream().map((e) -> template.collection(e).name()) + .reduce((a, b) -> a + ", " + b).get()) + .put("@vertex", type).get(), + new AqlQueryOptions(), type).asListRemaining(); } } From a0dfea927b71183e39a1bb2f4afb5f909d5d6664 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 26 Jan 2018 10:07:03 +0100 Subject: [PATCH 05/94] prepare release 1.0.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5574d6e12..ea758ae29 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.0.1-SNAPSHOT + 1.0.1 2017 jar From fac818948f8cb3070a1db34b085eebcf1f0335e2 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 26 Jan 2018 10:11:55 +0100 Subject: [PATCH 06/94] prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ea758ae29..1b18a6941 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.0.1 + 1.0.2-SNAPSHOT 2017 jar From 31844ac80bcea3560c5e09701e2c0dc2619131ce Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 19 Mar 2018 09:45:47 +0100 Subject: [PATCH 07/94] Fix ArangoRepository autowiring (Issue: #14 & #16) --- .../repository/ArangoRepositoryFactoryBean.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactoryBean.java b/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactoryBean.java index 77349ac00..962aa14c9 100644 --- a/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactoryBean.java +++ b/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactoryBean.java @@ -20,6 +20,7 @@ package com.arangodb.springframework.repository; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactorySupport; @@ -32,11 +33,16 @@ */ public class ArangoRepositoryFactoryBean, S> extends RepositoryFactoryBeanSupport { - private final ArangoOperations arangoOperations; - public ArangoRepositoryFactoryBean(final Class repositoryInterface, - final ArangoOperations arangoOperations) { + private ArangoOperations arangoOperations; + + @Autowired + public ArangoRepositoryFactoryBean(final Class repositoryInterface) { super(repositoryInterface); + } + + @Autowired + public void setArangoOperations(final ArangoOperations arangoOperations) { this.arangoOperations = arangoOperations; } From a9713d6ba1a516881b760bbcdd7757a543404578 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 22 Mar 2018 13:04:10 +0100 Subject: [PATCH 08/94] fix properties for jenkins --- src/test/resources/arangodb.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/arangodb.properties b/src/test/resources/arangodb.properties index cf509a8a5..5682618e6 100644 --- a/src/test/resources/arangodb.properties +++ b/src/test/resources/arangodb.properties @@ -1 +1 @@ -arangodb.hosts=127.0.0.1:8529 \ No newline at end of file +arangodb.hosts=127.0.0.1:8529 From 86717fce7736456bde025906a7de4831b8c98805 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 22 Mar 2018 16:25:27 +0100 Subject: [PATCH 09/94] fixed missing WITH information in derived query --- ChangeLog | 4 +++ .../repository/query/derived/Conjunction.java | 11 +++++- .../query/derived/ConjunctionBuilder.java | 21 ++++++++++-- .../query/derived/DerivedQueryCreator.java | 34 ++++++++++++++----- .../repository/query/derived/Disjunction.java | 11 +++++- .../query/derived/DisjunctionBuilder.java | 20 ++++++++--- .../query/derived/PartInformation.java | 11 +++++- 7 files changed, 92 insertions(+), 20 deletions(-) diff --git a/ChangeLog b/ChangeLog index 037eca990..5ecfb80ef 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +v1.0.2 (2018-03-xx) +--------------------------- +* fixed missing WITH information in derived query + v1.0.1 (2018-01-26) --------------------------- * fixed missing WITH information in AQL when resolving annotation @Relations (Issue #8) diff --git a/src/main/java/com/arangodb/springframework/repository/query/derived/Conjunction.java b/src/main/java/com/arangodb/springframework/repository/query/derived/Conjunction.java index 691d5279c..05f6355a5 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/derived/Conjunction.java +++ b/src/main/java/com/arangodb/springframework/repository/query/derived/Conjunction.java @@ -20,6 +20,8 @@ package com.arangodb.springframework.repository.query.derived; +import java.util.Collection; + /** * Created by F625633 on 24/07/2017. */ @@ -27,10 +29,12 @@ public class Conjunction { private final String array; private final String predicate; + private final Collection> with; - public Conjunction(final String array, final String predicate) { + public Conjunction(final String array, final String predicate, final Collection> with) { this.array = array; this.predicate = predicate; + this.with = with; } public String getArray() { @@ -52,4 +56,9 @@ public boolean hasPredicate() { public boolean isComposite() { return isArray() && hasPredicate(); } + + public Collection> getWith() { + return with; + } + } diff --git a/src/main/java/com/arangodb/springframework/repository/query/derived/ConjunctionBuilder.java b/src/main/java/com/arangodb/springframework/repository/query/derived/ConjunctionBuilder.java index 3efa68311..157c39848 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/derived/ConjunctionBuilder.java +++ b/src/main/java/com/arangodb/springframework/repository/query/derived/ConjunctionBuilder.java @@ -20,6 +20,9 @@ package com.arangodb.springframework.repository.query.derived; +import java.util.ArrayList; +import java.util.Collection; + /** * Created by F625633 on 24/07/2017. */ @@ -28,11 +31,20 @@ public class ConjunctionBuilder { private static final String ARRAY_DELIMITER = ", "; private static final String PREDICATE_DELIMITER = " AND "; - private final StringBuilder arrayStringBuilder = new StringBuilder(); - private final StringBuilder predicateStringBuilder = new StringBuilder(); + private final StringBuilder arrayStringBuilder; + private final StringBuilder predicateStringBuilder; + private final Collection> with; private int arrays = 0; + public ConjunctionBuilder() { + super(); + arrayStringBuilder = new StringBuilder(); + predicateStringBuilder = new StringBuilder(); + with = new ArrayList<>(); + arrays = 0; + } + public void add(final PartInformation partInformation) { if (partInformation.isArray()) { ++arrays; @@ -42,6 +54,9 @@ public void add(final PartInformation partInformation) { predicateStringBuilder.append( (predicateStringBuilder.length() == 0 ? "" : PREDICATE_DELIMITER) + partInformation.getClause()); } + if (partInformation.getWith() != null) { + with.addAll(partInformation.getWith()); + } } private String buildArrayString() { @@ -52,6 +67,6 @@ private String buildArrayString() { } public Conjunction build() { - return new Conjunction(buildArrayString(), predicateStringBuilder.toString()); + return new Conjunction(buildArrayString(), predicateStringBuilder.toString(), with); } } diff --git a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java index f23e4688e..8537525ce 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java +++ b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java @@ -20,12 +20,16 @@ package com.arangodb.springframework.repository.query.derived; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +41,7 @@ import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; +import org.springframework.data.mapping.PropertyPath; import org.springframework.data.mapping.context.PersistentPropertyPath; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; @@ -88,11 +93,7 @@ public DerivedQueryCreator(final ArangoMappingContext context, final Class do final boolean useFunctions) { super(tree, accessor); this.context = context; - String collectionName = context.getPersistentEntity(domainClass).getCollection(); - if (collectionName.split("-").length > 1) { - collectionName = "`" + collectionName + "`"; - } - this.collectionName = collectionName; + this.collectionName = collectionName(context.getPersistentEntity(domainClass).getCollection()); this.tree = tree; this.bindVars = bindVars; this.accessor = accessor; @@ -100,6 +101,10 @@ public DerivedQueryCreator(final ArangoMappingContext context, final Class do this.useFunctions = useFunctions; } + private static String collectionName(final String collection) { + return collection.contains("-") ? "`" + collection + "`" : collection; + } + public String getCollectionName() { return collectionName; } @@ -155,8 +160,8 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { final String array = disjunction.getArray().length() == 0 ? collectionName : disjunction.getArray(); final String predicate = disjunction.getPredicate().length() == 0 ? "" : " FILTER " + disjunction.getPredicate(); - final String queryTemplate = "FOR e IN %s%s%s%s%s%s%s"; // collection predicate count sort limit pageable - // queryType + final String queryTemplate = "%sFOR e IN %s%s%s%s%s%s%s"; // collection predicate count sort limit pageable + // queryType final String count = (tree.isCountProjection() || tree.isExistsProjection()) ? ((tree.isDistinct() ? " COLLECT entity = e" : "") + " COLLECT WITH COUNT INTO length") : ""; final String limit = tree.isLimiting() ? String.format(" LIMIT %d", tree.getMaxResults()) : ""; @@ -181,7 +186,11 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { sortString = distanceSortKey + ", " + sortString.substring(5, sortString.length()); } } - return String.format(queryTemplate, array, predicate, count, sortString, limit, pageable, type); + final String withCollections = disjunction.getWith().stream() + .map(c -> collectionName(context.getPersistentEntity(c).getCollection())).distinct() + .collect(Collectors.joining(", ")); + final String with = withCollections.isEmpty() ? "" : String.format("WITH %s ", withCollections); + return String.format(queryTemplate, with, array, predicate, count, sortString, limit, pageable, type); } /** @@ -769,7 +778,14 @@ private PartInformation createPartInformation(final Part part, final Iterator> with = new ArrayList<>(); + PropertyPath pp = part.getProperty(); + do { + Optional.ofNullable(pp.isCollection() ? pp.getOwningType().getComponentType() : pp.getOwningType()) + .filter(t -> context.getPersistentEntity(t) != null).map(t -> t.getType()) + .ifPresent(t -> with.add(t)); + } while ((pp = pp.next()) != null); + return clause == null ? null : new PartInformation(isArray, clause, with); } private String unsetDistance(final String clause) { diff --git a/src/main/java/com/arangodb/springframework/repository/query/derived/Disjunction.java b/src/main/java/com/arangodb/springframework/repository/query/derived/Disjunction.java index 258f2a680..0dd97d15b 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/derived/Disjunction.java +++ b/src/main/java/com/arangodb/springframework/repository/query/derived/Disjunction.java @@ -20,6 +20,8 @@ package com.arangodb.springframework.repository.query.derived; +import java.util.Collection; + /** * Created by F625633 on 24/07/2017. */ @@ -27,10 +29,12 @@ public class Disjunction { private final String array; private final String predicate; + private final Collection> with; - public Disjunction(final String array, final String predicate) { + public Disjunction(final String array, final String predicate, final Collection> with) { this.array = array; this.predicate = predicate; + this.with = with; } public String getArray() { @@ -40,4 +44,9 @@ public String getArray() { public String getPredicate() { return predicate; } + + public Collection> getWith() { + return with; + } + } diff --git a/src/main/java/com/arangodb/springframework/repository/query/derived/DisjunctionBuilder.java b/src/main/java/com/arangodb/springframework/repository/query/derived/DisjunctionBuilder.java index f92187b0c..c1ebc68fa 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/derived/DisjunctionBuilder.java +++ b/src/main/java/com/arangodb/springframework/repository/query/derived/DisjunctionBuilder.java @@ -20,6 +20,8 @@ package com.arangodb.springframework.repository.query.derived; +import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedList; /** @@ -33,15 +35,22 @@ public class DisjunctionBuilder { private final DerivedQueryCreator queryCreator; - private final LinkedList conjunctions = new LinkedList<>(); + private final LinkedList conjunctions; - private final StringBuilder arrayStringBuilder = new StringBuilder(); - private final StringBuilder predicateStringBuilder = new StringBuilder(); + private final StringBuilder arrayStringBuilder; + private final StringBuilder predicateStringBuilder; + private final Collection> with; - private int arrays = 0; + private int arrays; public DisjunctionBuilder(final DerivedQueryCreator queryCreator) { + super(); this.queryCreator = queryCreator; + conjunctions = new LinkedList<>(); + arrayStringBuilder = new StringBuilder(); + predicateStringBuilder = new StringBuilder(); + with = new ArrayList<>(); + arrays = 0; } public void add(final Conjunction conjunction) { @@ -56,6 +65,7 @@ public void add(final Conjunction conjunction) { predicateStringBuilder.append( (predicateStringBuilder.length() == 0 ? "" : PREDICATE_DELIMITER) + conjunction.getPredicate()); } + with.addAll(conjunction.getWith()); } private String buildArrayString() { @@ -90,6 +100,6 @@ private String buildPredicateSring() { public Disjunction build() { final String arrayString = String.format(buildArrayString(), queryCreator.getUniquePoint()[0], queryCreator.getUniquePoint()[1]); - return new Disjunction(arrayString, buildPredicateSring()); + return new Disjunction(arrayString, buildPredicateSring(), with); } } diff --git a/src/main/java/com/arangodb/springframework/repository/query/derived/PartInformation.java b/src/main/java/com/arangodb/springframework/repository/query/derived/PartInformation.java index 264d6fd71..9f09cd6e8 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/derived/PartInformation.java +++ b/src/main/java/com/arangodb/springframework/repository/query/derived/PartInformation.java @@ -20,6 +20,8 @@ package com.arangodb.springframework.repository.query.derived; +import java.util.Collection; + /** * Created by F625633 on 24/07/2017. */ @@ -27,10 +29,12 @@ public class PartInformation { private final boolean isArray; private final String clause; + private final Collection> with; - public PartInformation(final boolean isArray, final String clause) { + public PartInformation(final boolean isArray, final String clause, final Collection> with) { this.isArray = isArray; this.clause = clause; + this.with = with; } public boolean isArray() { @@ -40,4 +44,9 @@ public boolean isArray() { public String getClause() { return clause; } + + public Collection> getWith() { + return with; + } + } From 4b87b1b722fff4d1382f426e1253304ccea649c2 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 23 Mar 2018 12:48:54 +0100 Subject: [PATCH 10/94] Upgrade to Java driver 4.3.4 --- pom.xml | 2 +- .../springframework/core/template/ArangoExtCursor.java | 6 ++---- .../core/template/ArangoExtCursorIterator.java | 5 ++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 1b18a6941..e1a85a147 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 1.1.3 1.3 4.12 - 4.3.2 + 4.3.4 4.3.13.RELEASE ${spring.version} ${spring.version} diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java index 797013ddb..6f5909b7b 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java @@ -25,7 +25,6 @@ import com.arangodb.internal.ArangoCursorExecute; import com.arangodb.internal.ArangoCursorIterator; import com.arangodb.internal.InternalArangoDatabase; -import com.arangodb.internal.net.HostHandle; import com.arangodb.springframework.core.convert.ArangoConverter; /** @@ -46,8 +45,7 @@ protected ArangoCursorIterator createIterator( final ArangoCursor cursor, final InternalArangoDatabase db, final ArangoCursorExecute execute, - final CursorEntity result, - final HostHandle hostHandle) { - return new ArangoExtCursorIterator<>(cursor, db, execute, result, hostHandle); + final CursorEntity result) { + return new ArangoExtCursorIterator<>(cursor, db, execute, result); } } diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java index 6afc5a8bd..4f6ae08d3 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java @@ -25,7 +25,6 @@ import com.arangodb.internal.ArangoCursorExecute; import com.arangodb.internal.ArangoCursorIterator; import com.arangodb.internal.InternalArangoDatabase; -import com.arangodb.internal.net.HostHandle; import com.arangodb.springframework.core.convert.ArangoConverter; import com.arangodb.springframework.core.convert.DBEntity; import com.arangodb.velocypack.VPackSlice; @@ -40,8 +39,8 @@ class ArangoExtCursorIterator extends ArangoCursorIterator { private ArangoConverter converter; protected ArangoExtCursorIterator(final ArangoCursor cursor, final InternalArangoDatabase db, - final ArangoCursorExecute execute, final CursorEntity result, final HostHandle hostHandle) { - super(cursor, execute, db, result, hostHandle); + final ArangoCursorExecute execute, final CursorEntity result) { + super(cursor, execute, db, result); } public void setConverter(final ArangoConverter converter) { From 42ea33e96195826ffc3ed75e7e22d6fd79b3135d Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 23 Mar 2018 12:49:36 +0100 Subject: [PATCH 11/94] prepare release 1.0.2 --- ChangeLog | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5ecfb80ef..1b494ba80 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,4 @@ -v1.0.2 (2018-03-xx) +v1.0.2 (2018-03-23) --------------------------- * fixed missing WITH information in derived query diff --git a/pom.xml b/pom.xml index e1a85a147..06b0f40c9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.0.2-SNAPSHOT + 1.0.2 2017 jar From 85429cffef24ff19c59d7f463f08a5ca530c477a Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 23 Mar 2018 12:59:01 +0100 Subject: [PATCH 12/94] prepare snapshot version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 06b0f40c9..deeafc0b4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.0.2 + 1.0.3-SNAPSHOT 2017 jar From 66398aa4dd70de4eaa45b1f837724987e89f535c Mon Sep 17 00:00:00 2001 From: Christian Lechner Date: Thu, 29 Mar 2018 14:13:40 +0200 Subject: [PATCH 13/94] add DataIntegrityViolationException to ExceptionTranslator --- .../arangodb/springframework/core/util/ArangoErrors.java | 6 ++++++ .../core/util/ArangoExceptionTranslator.java | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/src/main/java/com/arangodb/springframework/core/util/ArangoErrors.java b/src/main/java/com/arangodb/springframework/core/util/ArangoErrors.java index 196a63b14..fb105d9a7 100644 --- a/src/main/java/com/arangodb/springframework/core/util/ArangoErrors.java +++ b/src/main/java/com/arangodb/springframework/core/util/ArangoErrors.java @@ -22,6 +22,7 @@ /** * @author Mark Vollmary + * @author Christian Lechner * */ public class ArangoErrors { @@ -51,6 +52,11 @@ public class ArangoErrors { */ public static final int ERROR_HTTP_METHOD_NOT_ALLOWED = 405; + /** + * conflict. Will be raised when a conflict is encountered. + */ + public static final int ERROR_HTTP_CONFLICT = 409; + /** * precondition failed. Will be raised when a precondition for an HTTP request is not met. */ diff --git a/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java b/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java index 85f466363..67247abe8 100644 --- a/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java +++ b/src/main/java/com/arangodb/springframework/core/util/ArangoExceptionTranslator.java @@ -22,6 +22,7 @@ import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.dao.PermissionDeniedDataAccessException; @@ -32,6 +33,7 @@ /** * @author Mark Vollmary + * @author Christian Lechner * */ public class ArangoExceptionTranslator implements PersistenceExceptionTranslator { @@ -57,6 +59,9 @@ public DataAccessException translateExceptionIfPossible(final RuntimeException e case ArangoErrors.ERROR_HTTP_NOT_FOUND: dae = new InvalidDataAccessResourceUsageException(exception.getMessage(), exception); break; + case ArangoErrors.ERROR_HTTP_CONFLICT: + dae = new DataIntegrityViolationException(exception.getMessage(), exception); + break; case ArangoErrors.ERROR_HTTP_PRECONDITION_FAILED: case ArangoErrors.ERROR_HTTP_SERVICE_UNAVAILABLE: dae = new DataAccessResourceFailureException(exception.getMessage(), exception); From a3bb3c87533928194f6fbcec8e14019d65cf3917 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 9 Apr 2018 08:17:39 +0200 Subject: [PATCH 14/94] Fix docs: Mapping conventions (Issue #10, #28) --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 09d7d0426..c8227303d 100644 --- a/README.md +++ b/README.md @@ -441,7 +441,13 @@ In this section we will describe the features and conventions for mapping Java o * The non-static fields of a Java object are used as fields in the stored document * The Java field name is mapped to the stored document field name * All nested Java object are stored as nested objects in the stored document -* The Java class needs a non parameterized constructor +* The Java class needs a constructor which meets the following criteria: + * in case of a single constructor: + * a non-parameterized constructor or + * a parameterized constructor + * in case of multiple constructors: + * a non-parameterized constructor or + * a parameterized constructor annotated with `@PersistenceConstructor` ## Type conventions From 5472c2bcc2964086bb91b60f4c455a8939ec7047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20Kov=C3=A1cs?= Date: Tue, 10 Apr 2018 11:39:11 +0200 Subject: [PATCH 15/94] template: added insert methods with collection name, query method avoid NPE when bindParams is null --- .../core/ArangoOperations.java | 80 ++++++++++++------- .../core/template/ArangoTemplate.java | 26 +++++- .../core/template/ArangoTemplateTest.java | 20 +++++ 3 files changed, 98 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/ArangoOperations.java b/src/main/java/com/arangodb/springframework/core/ArangoOperations.java index 80cfd1168..69301d8b3 100644 --- a/src/main/java/com/arangodb/springframework/core/ArangoOperations.java +++ b/src/main/java/com/arangodb/springframework/core/ArangoOperations.java @@ -67,7 +67,7 @@ public interface ArangoOperations { * @param query * contains the query string to be executed * @param bindVars - * key/value pairs representing the bind parameters + * key/value pairs representing the bind parameters, can be null * @param options * Additional options, can be null * @param entityClass @@ -75,8 +75,21 @@ public interface ArangoOperations { * @return cursor of the results * @throws DataAccessException */ - ArangoCursor query(String query, Map bindVars, AqlQueryOptions options, Class entityClass) - throws DataAccessException; + ArangoCursor query(String query, Map bindVars, AqlQueryOptions options, Class entityClass) throws DataAccessException; + + /** + * Create a cursor and return the first results. For queries without bind parameters. + * + * @param query + * contains the query string to be executed + * @param options + * Additional options, can be null + * @param entityClass + * The entity type of the result + * @return cursor of the results + * @throws DataAccessException + */ + ArangoCursor query(String query, AqlQueryOptions options, Class entityClass) throws DataAccessException; /** * Removes multiple document @@ -90,10 +103,7 @@ ArangoCursor query(String query, Map bindVars, AqlQueryOp * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity delete( - Iterable values, - Class entityClass, - DocumentDeleteOptions options) throws DataAccessException; + MultiDocumentEntity delete(Iterable values, Class entityClass, DocumentDeleteOptions options) throws DataAccessException; /** * Removes multiple document @@ -105,8 +115,7 @@ MultiDocumentEntity delete( * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity delete(Iterable values, Class entityClass) - throws DataAccessException; + MultiDocumentEntity delete(Iterable values, Class entityClass) throws DataAccessException; /** * Removes a document @@ -151,10 +160,7 @@ MultiDocumentEntity delete(Iterable values, Cl * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity update( - Iterable values, - Class entityClass, - DocumentUpdateOptions options) throws DataAccessException; + MultiDocumentEntity update(Iterable values, Class entityClass, DocumentUpdateOptions options) throws DataAccessException; /** * Partially updates documents, the documents to update are specified by the _key attributes in the objects on @@ -171,8 +177,7 @@ MultiDocumentEntity update( * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity update(Iterable values, Class entityClass) - throws DataAccessException; + MultiDocumentEntity update(Iterable values, Class entityClass) throws DataAccessException; /** * Partially updates the document identified by document id or key. The value must contain a document with the @@ -219,10 +224,7 @@ MultiDocumentEntity update(Iterable values, Cla * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity replace( - Iterable values, - Class entityClass, - DocumentReplaceOptions options) throws DataAccessException; + MultiDocumentEntity replace(Iterable values, Class entityClass, DocumentReplaceOptions options) throws DataAccessException; /** * Replaces multiple documents in the specified collection with the ones in the values, the replaced documents are @@ -239,8 +241,7 @@ MultiDocumentEntity replace( * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity replace(Iterable values, Class entityClass) - throws DataAccessException; + MultiDocumentEntity replace(Iterable values, Class entityClass) throws DataAccessException; /** * Replaces the document with key with the one in the body, provided there is such a document and no precondition is @@ -311,10 +312,7 @@ MultiDocumentEntity replace(Iterable values, Cl * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity insert( - Iterable values, - Class entityClass, - DocumentCreateOptions options) throws DataAccessException; + MultiDocumentEntity insert(Iterable values, Class entityClass, DocumentCreateOptions options) throws DataAccessException; /** * Creates new documents from the given documents, unless there is already a document with the _key given. If no @@ -329,8 +327,7 @@ MultiDocumentEntity insert( * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity insert(Iterable values, Class entityClass) - throws DataAccessException; + MultiDocumentEntity insert(Iterable values, Class entityClass) throws DataAccessException; /** * Creates a new document from the given document, unless there is already a document with the _key given. If no @@ -354,6 +351,35 @@ MultiDocumentEntity insert(Iterable values, Cla */ DocumentEntity insert(T value) throws DataAccessException; + /** + * Creates a new document from the given document, unless there is already a document with the _key given. If no + * _key is given, a new unique _key is generated automatically. + * + * @param collectionName + * Name of the collection in which the new document should be inserted + * @param value + * A representation of a single document + * @param options + * Additional options, can be null + * @return information about the document + * @throws DataAccessException + */ + DocumentEntity insert(String collectionName, Object value, DocumentCreateOptions options) throws DataAccessException; + + /** + + * Creates a new document from the given document, unless there is already a document with the _key given. If no + * _key is given, a new unique _key is generated automatically. + * + * @param collectionName + * Name of the collection in which the new document should be inserted + * @param value + * A representation of a single document + * @return information about the document + * @throws DataAccessException + */ + DocumentEntity insert(String collectionName, Object value) throws DataAccessException; + public enum UpsertStrategy { REPLACE, UPDATE } 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 f83a907cc..4a0d216de 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -305,13 +305,20 @@ public ArangoDBVersion getVersion() throws DataAccessException { } } + @Override + public ArangoCursor query(final String query, final AqlQueryOptions options, final Class entityClass) + throws DataAccessException { + return db().query(query, null, options, entityClass); + } + @Override public ArangoCursor query( final String query, final Map bindVars, final AqlQueryOptions options, final Class entityClass) throws DataAccessException { - return db().query(query, DBDocumentEntity.class.cast(toDBEntity(prepareBindVars(bindVars))), options, + return db().query(query, + bindVars == null ? null : DBDocumentEntity.class.cast(toDBEntity(prepareBindVars(bindVars))), options, entityClass); } @@ -517,6 +524,23 @@ public DocumentEntity insert(final Object value) throws DataAccessException { return insert(value, new DocumentCreateOptions()); } + @Override + public DocumentEntity insert(final String collectionName, final Object value, final DocumentCreateOptions options) + throws DataAccessException { + try { + final DocumentEntity res = _collection(collectionName).insertDocument(toDBEntity(value)); + updateDBFields(value, res); + return res; + } catch (final ArangoDBException e) { + throw exceptionTranslator.translateExceptionIfPossible(e); + } + } + + @Override + public DocumentEntity insert(final String collectionName, final Object value) throws DataAccessException { + return insert(collectionName, value, new DocumentCreateOptions()); + } + @Override public void upsert(final T value, final UpsertStrategy strategy) throws DataAccessException { final Class entityClass = value.getClass(); diff --git a/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java b/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java index d9cd27901..04027a8d7 100644 --- a/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java +++ b/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java @@ -94,6 +94,13 @@ public void insertDocuments() { assertThat(c3.getId(), is(notNullValue())); } + @Test + public void insertDocumentWithCollName() { + final DocumentEntity res = template.insert("customer", new Customer("John", "Doe", 30)); + assertThat(res, is(notNullValue())); + assertThat(res.getId(), is(notNullValue())); + } + @Test public void upsertReplace() { final Customer customer = new Customer("John", "Doe", 30); @@ -287,6 +294,19 @@ public void query() { assertThat(customers.get(0).getAge(), is(30)); } + @Test + public void queryWithoutBindParams() { + template.insert(new Customer("John", "Doe", 30)); + final ArangoCursor cursor = template.query("FOR c IN customer FILTER c.name == 'John' RETURN c", null, + new AqlQueryOptions(), Customer.class); + assertThat(cursor, is(notNullValue())); + final List customers = cursor.asListRemaining(); + assertThat(customers.size(), is(1)); + assertThat(customers.get(0).getName(), is("John")); + assertThat(customers.get(0).getSurname(), is("Doe")); + assertThat(customers.get(0).getAge(), is(30)); + } + @SuppressWarnings("rawtypes") @Test public void queryMap() { From 84b9092b2944711ff3b67400f758d9612c848512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gergely=20Kov=C3=A1cs?= Date: Tue, 10 Apr 2018 11:47:11 +0200 Subject: [PATCH 16/94] fix indentation --- .../core/ArangoOperations.java | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/ArangoOperations.java b/src/main/java/com/arangodb/springframework/core/ArangoOperations.java index 69301d8b3..1ad867b60 100644 --- a/src/main/java/com/arangodb/springframework/core/ArangoOperations.java +++ b/src/main/java/com/arangodb/springframework/core/ArangoOperations.java @@ -75,7 +75,8 @@ public interface ArangoOperations { * @return cursor of the results * @throws DataAccessException */ - ArangoCursor query(String query, Map bindVars, AqlQueryOptions options, Class entityClass) throws DataAccessException; + ArangoCursor query(String query, Map bindVars, AqlQueryOptions options, Class entityClass) + throws DataAccessException; /** * Create a cursor and return the first results. For queries without bind parameters. @@ -103,7 +104,10 @@ public interface ArangoOperations { * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity delete(Iterable values, Class entityClass, DocumentDeleteOptions options) throws DataAccessException; + MultiDocumentEntity delete( + Iterable values, + Class entityClass, + DocumentDeleteOptions options) throws DataAccessException; /** * Removes multiple document @@ -115,7 +119,8 @@ public interface ArangoOperations { * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity delete(Iterable values, Class entityClass) throws DataAccessException; + MultiDocumentEntity delete(Iterable values, Class entityClass) + throws DataAccessException; /** * Removes a document @@ -160,7 +165,10 @@ public interface ArangoOperations { * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity update(Iterable values, Class entityClass, DocumentUpdateOptions options) throws DataAccessException; + MultiDocumentEntity update( + Iterable values, + Class entityClass, + DocumentUpdateOptions options) throws DataAccessException; /** * Partially updates documents, the documents to update are specified by the _key attributes in the objects on @@ -177,7 +185,8 @@ public interface ArangoOperations { * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity update(Iterable values, Class entityClass) throws DataAccessException; + MultiDocumentEntity update(Iterable values, Class entityClass) + throws DataAccessException; /** * Partially updates the document identified by document id or key. The value must contain a document with the @@ -224,7 +233,10 @@ public interface ArangoOperations { * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity replace(Iterable values, Class entityClass, DocumentReplaceOptions options) throws DataAccessException; + MultiDocumentEntity replace( + Iterable values, + Class entityClass, + DocumentReplaceOptions options) throws DataAccessException; /** * Replaces multiple documents in the specified collection with the ones in the values, the replaced documents are @@ -241,7 +253,8 @@ public interface ArangoOperations { * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity replace(Iterable values, Class entityClass) throws DataAccessException; + MultiDocumentEntity replace(Iterable values, Class entityClass) + throws DataAccessException; /** * Replaces the document with key with the one in the body, provided there is such a document and no precondition is @@ -312,7 +325,10 @@ public interface ArangoOperations { * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity insert(Iterable values, Class entityClass, DocumentCreateOptions options) throws DataAccessException; + MultiDocumentEntity insert( + Iterable values, + Class entityClass, + DocumentCreateOptions options) throws DataAccessException; /** * Creates new documents from the given documents, unless there is already a document with the _key given. If no @@ -327,7 +343,8 @@ public interface ArangoOperations { * @return information about the documents * @throws DataAccessException */ - MultiDocumentEntity insert(Iterable values, Class entityClass) throws DataAccessException; + MultiDocumentEntity insert(Iterable values, Class entityClass) + throws DataAccessException; /** * Creates a new document from the given document, unless there is already a document with the _key given. If no @@ -364,7 +381,8 @@ public interface ArangoOperations { * @return information about the document * @throws DataAccessException */ - DocumentEntity insert(String collectionName, Object value, DocumentCreateOptions options) throws DataAccessException; + DocumentEntity insert(String collectionName, Object value, DocumentCreateOptions options) + throws DataAccessException; /** From c33a3edbe464a86df10064227acfc46197fcedb9 Mon Sep 17 00:00:00 2001 From: Christian Lechner <6638938+christian-lechner@users.noreply.github.com> Date: Tue, 17 Apr 2018 16:09:41 +0200 Subject: [PATCH 17/94] Fix race conditions in ArangoTemplate when creating database and collections (#35) * fix race conditions when creating db and collections * move return stmt out of sync block * tweak concurrent hash map parameters * Fix dropDatabase() & NPE protection Don't call db() in dropDatabse() as it would create the db (if there is none) and would then drop it. * make database field volatile so that other threads see changes instantly --- .../core/template/ArangoTemplate.java | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) 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 4a0d216de..76b137406 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -28,6 +28,7 @@ import java.util.Iterator; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import org.springframework.dao.DataAccessException; @@ -77,6 +78,7 @@ /** * @author Mark Vollmary + * @author Christian Lechner * */ public class ArangoTemplate implements ArangoOperations, CollectionCallback { @@ -85,7 +87,7 @@ public class ArangoTemplate implements ArangoOperations, CollectionCallback { private final PersistenceExceptionTranslator exceptionTranslator; private final ArangoConverter converter; private final ArangoDB arango; - private ArangoDatabase database; + private volatile ArangoDatabase database; private final String databaseName; private final Map collectionCache; @@ -106,15 +108,26 @@ public ArangoTemplate(final ArangoDB.Builder arango, final String database, fina this.databaseName = database; this.converter = converter; this.exceptionTranslator = exceptionTranslator; - collectionCache = new HashMap<>(); + // set concurrency level to 1 as writes are very rare compared to reads + collectionCache = new ConcurrentHashMap<>(8, 0.9f, 1); version = null; } private ArangoDatabase db() { - if (database == null) { - database = arango.db(databaseName); + // guard against NPE because database can be set to null by dropDatabase() by another thread + ArangoDatabase db = database; + if (db != null) { + return db; + } + // make sure the database is only created once + synchronized (this) { + db = database; + if (db != null) { + return db; + } + db = arango.db(databaseName); try { - database.getInfo(); + db.getInfo(); } catch (final ArangoDBException e) { if (new Integer(404).equals(e.getResponseCode())) { try { @@ -126,8 +139,9 @@ private ArangoDatabase db() { throw translateExceptionIfPossible(e); } } + database = db; + return db; } - return database; } private DataAccessException translateExceptionIfPossible(final RuntimeException exception) { @@ -153,15 +167,15 @@ private ArangoCollection _collection( final String name, final ArangoPersistentEntity persistentEntity, final CollectionCreateOptions options) { - ArangoCollection collection = collectionCache.get(name); - if (collection == null) { - collection = db().collection(name); + + return collectionCache.computeIfAbsent(name, collName -> { + ArangoCollection collection = db().collection(collName); try { collection.getInfo(); } catch (final ArangoDBException e) { if (new Integer(404).equals(e.getResponseCode())) { try { - db().createCollection(name, options); + db().createCollection(collName, options); } catch (final ArangoDBException e1) { throw translateExceptionIfPossible(e1); } @@ -169,12 +183,11 @@ private ArangoCollection _collection( throw translateExceptionIfPossible(e); } } - collectionCache.put(name, collection); if (persistentEntity != null) { ensureCollectionIndexes(collection(collection), persistentEntity); } - } - return collection; + return collection; + }); } private static void ensureCollectionIndexes( @@ -652,8 +665,13 @@ public boolean exists(final String id, final Class entityClass) throws DataAc @Override public void dropDatabase() throws DataAccessException { + // guard against NPE because another thread could also call dropDatabase() + ArangoDatabase db = database; + if (db == null) { + db = arango.db(databaseName); + } try { - db().drop(); + db.drop(); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } From d89098d1a231ae11d4930abcecea9d14df000e3e Mon Sep 17 00:00:00 2001 From: Christian Lechner <6638938+christian-lechner@users.noreply.github.com> Date: Tue, 17 Apr 2018 16:13:58 +0200 Subject: [PATCH 18/94] Type Mapper implementation & Custom Conversions extension (#33) * add type mapper & extend custom conversions * convert map and collection values if necessary * additional test cases added * add type key method & make non-bean methods protected * remove test module * add type mapping chapter to readme * fix spelling * make sure that component type is never null * changed default type key to _class * java.time converters should not check if JodaTime is present * DBEntity module added * DBEntityDeserializer moved into DBEntityModule * register DBEntityModule * made parameters final * removed unused class * readme updated * add simple types test case * add simple type converters * update arango simple types * change converter to make use of array simple types * formatting --- docs/Drivers/SpringData/Reference/README.md | 705 ++++++++++++++++++ .../config/AbstractArangoConfiguration.java | 34 +- .../config/ArangoEntityClassScanner.java | 8 + .../core/convert/ArangoConverter.java | 3 + .../core/convert/ArangoCustomConversions.java | 1 + .../convert/ArangoSimpleTypeConverters.java | 106 +++ .../core/convert/ArangoTypeMapper.java | 33 + .../core/convert/DBEntityModule.java | 61 ++ .../core/convert/DefaultArangoConverter.java | 220 +++--- .../core/convert/DefaultArangoTypeMapper.java | 104 +++ .../core/convert/TimeStringConverters.java | 8 +- .../core/mapping/ArangoSimpleTypes.java | 97 +++ .../core/template/ArangoTemplate.java | 6 +- .../core/mapping/ArangoMappingTest.java | 383 +++++++++- 14 files changed, 1654 insertions(+), 115 deletions(-) create mode 100644 docs/Drivers/SpringData/Reference/README.md create mode 100644 src/main/java/com/arangodb/springframework/core/convert/ArangoSimpleTypeConverters.java create mode 100644 src/main/java/com/arangodb/springframework/core/convert/ArangoTypeMapper.java create mode 100644 src/main/java/com/arangodb/springframework/core/convert/DBEntityModule.java create mode 100644 src/main/java/com/arangodb/springframework/core/convert/DefaultArangoTypeMapper.java create mode 100644 src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java diff --git a/docs/Drivers/SpringData/Reference/README.md b/docs/Drivers/SpringData/Reference/README.md new file mode 100644 index 000000000..1b729a561 --- /dev/null +++ b/docs/Drivers/SpringData/Reference/README.md @@ -0,0 +1,705 @@ +# Spring Data ArangoDB - Reference + +# Template + +With `ArangoTemplate` Spring Data ArangoDB offers a central support for interactions with the database over a rich feature set. It mostly offers the features from the ArangoDB Java driver with additional exception translation from the drivers exceptions to the Spring Data access exceptions inheriting the `DataAccessException` class. +The `ArangoTemplate` class is the default implementation of the operations interface `ArangoOperations` which developers of Spring Data are encouraged to code against. + +# Repositories + +## Introduction + +Spring Data Commons provides a composable repository infrastructure which Spring Data ArangoDB is built on. These allow for interface-based composition of repositories consisting of provided default implementations for certain interfaces (like `CrudRepository`) and custom implementations for other methods. + +## Instantiating + +Instances of a Repository are created in Spring beans through the auto-wired mechanism of Spring. + +```java +public class MySpringBean { + + @Autowired + private MyRepository rep; + +} +``` + +## Return types + +The method return type for single results can be a primitive type, a domain class, `Map`, `BaseDocument`, `BaseEdgeDocument`, `Optional`, `GeoResult`. +The method return type for multiple results can additionally be `ArangoCursor`, `Iterable`, `Collection`, `List`, `Set`, `Page`, `Slice`, `GeoPage`, `GeoResults` where Type can be everything a single result can be. + +## Query methods + +Queries using [ArangoDB Query Language (AQL)](https://docs.arangodb.com/current/AQL/index.html) can be supplied with the `@Query` annotation on methods. `AqlQueryOptions` can also be passed to the driver, as an argument anywhere in the method signature. + +There are three ways of passing bind parameters to the query in the query annotation. + +Using number matching, arguments will be substituted into the query in the order they are passed to the query method. + +```java +public interface MyRepository extends Repository{ + + @Query("FOR c IN customers FILTER c.name == @0 AND c.surname == @2 RETURN c") + ArangoCursor query(String name, AqlQueryOptions options, String surname); + +} +``` + +With the `@Param` annotation, the argument will be placed in the query at the place corresponding to the value passed to the `@Param` annotation. + +```java +public interface MyRepository extends Repository{ + + @Query("FOR c IN customers FILTER c.name == @name AND c.surname == @surname RETURN c") + ArangoCursor query(@Param("name") String name, @Param("surname") String surname); + +} +``` + + In addition you can use a parameter of type `Map` annotated with `@BindVars` as your bind parameters. You can then fill the map with any parameter used in the query. (see [here](https://docs.arangodb.com/3.1/AQL/Fundamentals/BindParameters.html#bind-parameters) for more Information about Bind Parameters). + +```java +public interface MyRepository extends Repository{ + + @Query("FOR c IN customers FILTER c.name == @name AND c.surname = @surname RETURN c") + ArangoCursor query(@BindVars Map bindVars); + +} +``` + +A mixture of any of these methods can be used. Parameters with the same name from an `@Param` annotation will override those in the `bindVars`. + +```java +public interface MyRepository extends Repository{ + + @Query("FOR c IN customers FILTER c.name == @name AND c.surname = @surname RETURN c") + ArangoCursor query(@BindVars Map bindVars, @Param("name") String name); + +} +``` + +## Derived queries + +Spring Data ArangoDB supports queries derived from methods names by splitting it into its semantic parts and converting into AQL. The mechanism strips the prefixes `find..By`, `get..By`, `query..By`, `read..By`, `stream..By`, `count..By`, `exists..By`, `delete..By`, `remove..By` from the method and parses the rest. The By acts as a separator to indicate the start of the criteria for the query to be built. You can define conditions on entity properties and concatenate them with `And` and `Or`. + +The complete list of part types for derived methods is below, where doc is a document in the database + +Keyword | Sample | Predicate +----------|----------------|-------- +IsGreaterThan, GreaterThan, After | findByAgeGreaterThan(int age) | doc.age > age +IsGreaterThanEqual, GreaterThanEqual | findByAgeIsGreaterThanEqual(int age) | doc.age >= age +IsLessThan, LessThan, Before | findByAgeIsLessThan(int age) | doc.age < age +IsLessThanEqualLessThanEqual | findByAgeLessThanEqual(int age) | doc.age <= age +IsBetween, Between | findByAgeBetween(int lower, int upper) | lower < doc.age < upper +IsNotNull, NotNull | findByNameNotNull() | doc.name != null +IsNull, Null | findByNameNull() | doc.name == null +IsLike, Like | findByNameLike(String name) | doc.name LIKE name +IsNotLike, NotLike | findByNameNotLike(String name) | NOT(doc.name LIKE name) +IsStartingWith, StartingWith, StartsWith | findByNameStartsWith(String prefix) | doc.name LIKE prefix +IsEndingWith, EndingWith, EndsWith | findByNameEndingWith(String suffix) | doc.name LIKE suffix +Regex, MatchesRegex, Matches | findByNameRegex(String pattern) | REGEX_TEST(doc.name, name, ignoreCase) +(No Keyword) | findByFirstName(String name) | doc.name == name +IsTrue, True | findByActiveTrue() | doc.active == true +IsFalse, False | findByActiveFalse() | doc.active == false +Is, Equals | findByAgeEquals(int age) | doc.age == age +IsNot, Not | findByAgeNot(int age) | doc.age != age +IsIn, In | findByNameIn(String[] names) | doc.name IN names +IsNotIn, NotIn | findByNameIsNotIn(String[] names) | doc.name NOT IN names +IsContaining, Containing, Contains | findByFriendsContaining(String name) | name IN doc.friends +IsNotContaining, NotContaining, NotContains | findByFriendsNotContains(String name) | name NOT IN doc.friends +Exists | findByFriendNameExists() | HAS(doc.friend, name) + + +```java +public interface MyRepository extends Repository { + + // FOR c IN customers FILTER c.name == @0 RETURN c + ArangoCursor findByName(String name); + ArangoCursor getByName(String name); + + // FOR c IN customers + // FILTER c.name == @0 && c.age == @1 + // RETURN c + ArangoCursor findByNameAndAge(String name, int age); + + // FOR c IN customers + // FILTER c.name == @0 || c.age == @1 + // RETURN c + ArangoCursor findByNameOrAge(String name, int age); +} +``` + +You can apply sorting for one or multiple sort criteria by appending `OrderBy` to the method and `Asc` or `Desc` for the directions. + +```java +public interface MyRepository extends Repository { + + // FOR c IN customers + // FITLER c.name == @0 + // SORT c.age DESC RETURN c + ArangoCursor getByNameOrderByAgeDesc(String name); + + // FOR c IN customers + // FILTER c.name = @0 + // SORT c.name ASC, c.age DESC RETURN c + ArangoCursor findByNameOrderByNameAscAgeDesc(String name); + +} +``` + +### Geospatial queries + +Geospatial queries are a subsection of derived queries. To use a geospatial query on a collection, a geo index must exist on that collection. A geo index can be created on a field which is a two element array, corresponding to latitude and longitude coordinates. + +As a subsection of derived queries, geospatial queries support all the same return types, but also support the three return types `GeoPage, GeoResult and Georesults`. These types must be used in order to get the distance of each document as generated by the query. + +There are two kinds of geospatial query, Near and Within. Near sorts documents by distance from the given point, while within both sorts and filters documents, returning those within the given distance range or shape. + +```java +public interface MyRepository extends Repository { + + GeoResult getByLocationNear(Point point); + + GeoResults findByLocationWithinOrLocationWithin(Box box, Polygon polygon); + + //Equivalent queries + GeoResults findByLocationWithinOrLocationWithin(Point point, int distance); + GeoResults findByLocationWithinOrLocationWithin(Point point, Distance distance); + GeoResults findByLocationWithinOrLocationWithin(Circle circle); + +} +``` + +## Property expression + +Property expressions can refer only to direct and nested properties of the managed domain class. The algorithm checks the domain class for the entire expression as the property. If the check fails, the algorithm splits up the expression at the camel case parts from the right and tries to find the corresponding property. + +```java +@Document("customers") +public class Customer { + private Address address; +} + +public class Address { + private ZipCode zipCode; +} + +public interface MyRepository extends Repository { + + // 1. step: search domain class for a property "addressZipCode" + // 2. step: search domain class for "addressZip.code" + // 3. step: search domain class for "address.zipCode" + ArangoCursor findByAddressZipCode(ZipCode zipCode); +} +``` + +It is possible for the algorithm to select the wrong property if the domain class also has a property which matches the first split of the expression. To resolve this ambiguity you can use _ as a separator inside your method-name to define traversal points. + +```java +@Document("customers") +public class Customer { + private Address address; + private AddressZip addressZip; +} + +public class Address { + private ZipCode zipCode; +} + +public class AddressZip { + private String code; +} + +public interface MyRepository extends Repository { + + // 1. step: search domain class for a property "addressZipCode" + // 2. step: search domain class for "addressZip.code" + // creates query with "x.addressZip.code" + ArangoCursor findByAddressZipCode(ZipCode zipCode); + + // 1. step: search domain class for a property "addressZipCode" + // 2. step: search domain class for "addressZip.code" + // 3. step: search domain class for "address.zipCode" + // creates query with "x.address.zipCode" + ArangoCursor findByAddress_ZipCode(ZipCode zipCode); + +} +``` + +## Special parameter handling + +### Bind parameters + +AQL supports the usage of [bind parameters](https://docs.arangodb.com/3.1/AQL/Fundamentals/BindParameters.html) which you can define with a method parameter named `bindVars` of type `Map`. + +```java +public interface MyRepository extends Repository { + + @Query("FOR c IN customers FILTER c[@field] == @value RETURN c") + ArangoCursor query(Map bindVars); + +} + +Map bindVars = new HashMap(); +bindVars.put("field", "name"); +bindVars.put("value", "john"; + +// will execute query "FOR c IN customers FILTER c.name == "john" RETURN c" +ArangoCursor cursor = myRepo.query(bindVars); +``` + +### AQL query options + +You can set additional options for the query and the created cursor over the class `AqlQueryOptions` which you can simply define as a method parameter without a specific name. AqlQuery options can also be defined with the `@QueryOptions` annotation, as shown below. AqlQueryOptions from an annotation and those from an argument are merged if both exist, with those in the argument taking precedence. + +The `AqlQueryOptions` allows you to set the cursor time-to-life, batch-size, caching flag and several other settings. This special parameter works with both query-methods and finder-methods. Keep in mind that some options, like time-to-life, are only effective if the method return type is`ArangoCursor` or `Iterable`. + +```java +public interface MyRepository extends Repository { + + + @Query("FOR c IN customers FILTER c.name == @0 RETURN c") + Iterable query(String name, AqlQueryOptions options); + + + Iterable findByName(String name, AqlQueryOptions options); + + + @QueryOptions(maxPlans = 1000, ttl = 128) + ArangoCursor findByAddressZipCode(ZipCode zipCode); + + + @Query("FOR c IN customers FILTER c[@field] == @value RETURN c") + @QueryOptions(cache = true, ttl = 128) + ArangoCursor query(Map bindVars, AqlQueryOptions options); + +} +``` + +# Mapping + +## Introduction + +In this section we will describe the features and conventions for mapping Java objects to documents and how to override those conventions with annotation based mapping metadata. + +## Conventions + +* The Java class name is mapped to the collection name +* The non-static fields of a Java object are used as fields in the stored document +* The Java field name is mapped to the stored document field name +* All nested Java object are stored as nested objects in the stored document +* The Java class needs a constructor which meets the following criteria: + * in case of a single constructor: + * a non-parameterized constructor or + * a parameterized constructor + * in case of multiple constructors: + * a non-parameterized constructor or + * a parameterized constructor annotated with `@PersistenceConstructor` + +## Type conventions + +ArangoDB uses [VelocyPack](https://github.com/arangodb/velocypack) as it's internal storage format which supports a large number of data types. In addition Spring Data ArangoDB offers - with the underlying Java driver - built-in converters to add additional types to the mapping. + +Java type | VelocyPack type +----------|---------------- +java.lang.String | string +java.lang.Boolean | bool +java.lang.Integer | signed int 4 bytes, smallint +java.lang.Long | signed int 8 bytes, smallint +java.lang.Short | signed int 2 bytes, smallint +java.lang.Double | double +java.lang.Float | double +java.math.BigInteger | signed int 8 bytes, unsigned int 8 bytes +java.math.BigDecimal | double +java.lang.Number | double +java.lang.Character | string +java.util.Date | string (date-format ISO 8601) +java.sql.Date | string (date-format ISO 8601) +java.sql.Timestamp | string (date-format ISO 8601) +java.util.UUID | string +java.lang.byte[] | string (Base64) + +## Type mapping +As collections in ArangoDB can contain documents of various types, a mechanism to retrieve the correct Java class is required. The type information of properties declared in a class may not be enough to restore the original class (due to inheritance). If the declared complex type and the actual type do not match, information about the actual type is stored together with the document. This is necessary to restore the correct type when reading from the DB. Consider the following example: + +```java +public class Person { + private String name; + private Address homeAddress; + // ... + + // getters and setters omitted +} + +public class Employee extends Person { + private Address workAddress; + // ... + + // getters and setters omitted +} + +public class Address { + private final String street; + private final String number; + // ... + + public Address(String street, String number) { + this.street = street; + this.number = number; + } + + // getters omitted +} + +@Document +public class Company { + @Key + private String key; + private Person manager; + + // getters and setters omitted +} + +Employee manager = new Employee(); +manager.setName("Jane Roberts"); +manager.setHomeAddress(new Address("Park Avenue", "432/64")); +manager.setWorkAddress(new Address("Main Street", "223")); +Company comp = new Company(); +comp.setManager(manager); +``` + +The serialized document for the DB looks like this: + +```json +{ + "manager": { + "name": "Jane Roberts", + "homeAddress": { + "street": "Park Avenue", + "number": "432/64" + }, + "workAddress": { + "street": "Main Street", + "number": "223" + }, + "_class": "com.arangodb.Employee" + }, + "_class": "com.arangodb.Company" +} +``` + +Type hints are written for top-level documents (as a collection can contain different document types) as well as for every value if it's a complex type and a sub-type of the property type declared. `Map`s and `Collection`s are excluded from type mapping. Without the additional information about the concrete classes used, the document couldn't be restored in Java. The type information of the `manager` property is not enough to determine the `Employee` type. The `homeAddress` and `workAddress` properties have the same actual and defined type, thus no type hint is needed. + +### Customizing type mapping +By default, the fully qualified class name is stored in the documents as a type hint. A custom type hint can be set with the `@TypeAlias("my-alias")` annotation on an entity. Make sure that it is an unique identifier across all entities. If we would add a `TypeAlias("employee")` annotation to the `Employee` class above, it would be persisted as `"_class": "employee"`. + +The default type key is `_class` and can be changed by overriding the `typeKey()` method of the `AbstractArangoConfiguration` class. + +If you need to further customize the type mapping process, the `arangoTypeMapper()` method of the configuration class can be overridden. The included `DefaultArangoTypeMapper` can be customized by providing a list of [`TypeInformationMapper`](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/convert/TypeInformationMapper.html)s that create aliases from types and vice versa. + +In order to fully customize the type mapping process you can provide a custom type mapper implementation by extending the `DefaultArangoTypeMapper` class. + +### Deactivating type mapping +To deactivate the type mapping process, you can return `null` from the `typeKey()` method of the `AbstractArangoConfiguration` class. No type hints are stored in the documents with this setting. If you make sure that each defined type corresponds to the actual type, you can disable the type mapping, otherwise it can lead to exceptions when reading the entities from the DB. + +## Annotations + +### Annotation overview + +annotation | level | description +-----------|-------|------------ +@Document | class | marks this class as a candidate for mapping +@Edge | class | marks this class as a candidate for mapping +@Id | field | stores the field as the system field _id +@Key | field | stores the field as the system field _key +@Rev | field | stores the field as the system field _rev +@Field("alt-name") | field | stores the field with an alternative name +@Ref | field | stores the _id of the referenced document and not the nested document +@From | field | stores the _id of the referenced document as the system field _from +@To | field | stores the _id of the referenced document as the system field _to +@Relations | field | vertices which are connected over edges +@Transient | field, method, annotation | marks a field to be transient for the mapping framework, thus the property will not be persisted and not further inspected by the mapping framework +@PersistenceConstructor | constructor | marks a given constructor - even a package protected one - to use when instantiating the object from the database +@TypeAlias("alias") | class | set a type alias for the class when persisted to the DB +@HashIndex | class | describes a hash index +@HashIndexed | field | describes how to index the field +@SkiplistIndex | class | describes a skiplist index +@SkiplistIndexed | field | describes how to index the field +@PersistentIndex | class | describes a persistent index +@PersistentIndexed | field | describes how to index the field +@GeoIndex | class | describes a geo index +@GeoIndexed | field | describes how to index the field +@FulltextIndex | class | describes a fulltext index +@FulltextIndexed | field | describes how to index the field + +### Document + +The annotations `@Document` applied to a class marks this class as a candidate for mapping to the database. The most relevant parameter is `value` to specify the collection name in the database. The annotation `@Document` specifies the collection type to `DOCUMENT`. + +```java +@Document(value="persons") +public class Person { + ... +} +``` + +### Edge + +The annotations `@Edge` applied to a class marks this class as a candidate for mapping to the database. The most relevant parameter is `value` to specify the collection name in the database. The annotation `@Edge` specifies the collection type to `EDGE`. + +```java +@Edge("relations") +public class Relation { + ... +} +``` + +### Reference + +With the annotation `@Ref` applied on a field the nested object isn’t stored as a nested object in the document. The `_id` field of the nested object is stored in the document and the nested object has to be stored as a separate document in another collection described in the `@Document` annotation of the nested object class. To successfully persist an instance of your object the referencing field has to be null or it's instance has to provide a field with the annotation `@Id` including a valid id. + +```java +@Document(value="persons") +public class Person { + @Ref + private Address address; +} + +@Document("addresses") +public class Address { + @Id + private String id; + private String country; + private String street; +} +``` + +The database representation of `Person` in collection *persons* looks as follow: + +``` +{ + "_key" : "123", + "_id" : "persons/123", + "address" : "addresses/456" +} +``` +and the representation of `Address` in collection *addresses*: +``` +{ + "_key" : "456", + "_id" : "addresses/456", + "country" : "...", + "street" : "..." +} +``` + +Without the annotation `@Ref` at the field `address`, the stored document would look: + +``` +{ + "_key" : "123", + "_id" : "persons/123", + "address" : { + "country" : "...", + "street" : "..." + } +} +``` + +### Relations + +With the annotation `@Relations` applied on a collection or array field in a class annotated with `@Document` the nested objects are fetched from the database over a graph traversal with your current object as the starting point. The most relevant parameter is `edge`. With `edge` you define the edge collection - which should be used in the traversal - using the class type. With the parameter `depth` you can define the maximal depth for the traversal (default 1) and the parameter `direction` defines whether the traversal should follow outgoing or incoming edges (default Direction.ANY). + +```java +@Document(value="persons") +public class Person { + @Relations(edge=Relation.class, depth=1, direction=Direction.ANY) + private List friends; +} + +@Edge(name="relations") +public class Relation { + +} +``` + +### Document with From and To + +With the annotations `@From` and `@To` applied on a collection or array field in a class annotated with `@Document` the nested edge objects are fetched from the database. Each of the nested edge objects has to be stored as separate edge document in the edge collection described in the `@Edge` annotation of the nested object class with the *_id* of the parent document as field *_from* or *_to*. + +```java +@Document("persons") +public class Person { + @From + private List relations; +} + +@Edge(name="relations") +public class Relation { + ... +} +``` + +The database representation of `Person` in collection *persons* looks as follow: +``` +{ + "_key" : "123", + "_id" : "persons/123" +} +``` + +and the representation of `Relation` in collection *relations*: +``` +{ + "_key" : "456", + "_id" : "relations/456", + "_from" : "persons/123" + "_to" : ".../..." +} +{ + "_key" : "789", + "_id" : "relations/456", + "_from" : "persons/123" + "_to" : ".../..." +} +... + +``` + +### Edge with From and To + +With the annotations `@From` and `@To` applied on a field in a class annotated with `@Edge` the nested object is fetched from the database. The nested object has to be stored as a separate document in the collection described in the `@Document` annotation of the nested object class. The *_id* field of this nested object is stored in the fields `_from` or `_to` within the edge document. + +```java +@Edge("relations") +public class Relation { + @From + private Person c1; + @To + private Person c2; +} + +@Document(value="persons") +public class Person { + @Id + private String id; +} +``` + +The database representation of `Relation` in collection *relations* looks as follow: +``` +{ + "_key" : "123", + "_id" : "relations/123", + "_from" : "persons/456", + "_to" : "persons/789" +} +``` + +and the representation of `Person` in collection *persons*: +``` +{ + "_key" : "456", + "_id" : "persons/456", +} +{ + "_key" : "789", + "_id" : "persons/789", +} +``` + +**Note:** If you want to save an instance of `Relation`, both `Person` objects (from & to) already have to be persisted and the class `Person` needs a field with the annotation `@Id` so it can hold the persisted `_id` from the database. + +### Index and Indexed annotations + +With the `@Indexed` annotations user defined indexes can be created at a collection level by annotating single fields of a class. + +Possible `@Indexed` annotations are: +* `@HashIndexed` +* `@SkiplistIndexed` +* `@PersistentIndexed` +* `@GeoIndexed` +* `@FulltextIndexed` + +The following example creates a hash index on the field `name` and a separate hash index on the field `age`: +```java +public class Person { + @HashIndexed + private String name; + + @HashIndexed + private int age; +} +``` + +With the `@Indexed` annotations different indexes can be created on the same field. + +The following example creates a hash index and also a skiplist index on the field `name`: +```java +public class Person { + @HashIndexed + @SkiplistIndexed + private String name; +} +``` + +If the index should include multiple fields the `@Index` annotations can be used on the type instead. + +Possible `@Index` annotations are: +* `@HashIndex` +* `@SkiplistIndex` +* `@PersistentIndex` +* `@GeoIndex` +* `@FulltextIndex` + +The following example creates a single hash index on the fields `name` and `age`, note that if a field is renamed in the database with @Field, the new field name must be used in the index declaration: +```java +@HashIndex(fields = {"fullname", "age"}) +public class Person { + @Field("fullname") + private String name; + + private int age; +} +``` + +The `@Index` annotations can also be used to create an index on a nested field. + +The following example creates a single hash index on the fields `name` and `address.country`: +```java +@HashIndex(fields = {"name", "address.country"}) +public class Person { + private String name; + + private Address address; +} +``` + +The `@Index` annotations and the `@Indexed` annotations can be used at the same time in one class. + +The following example creates a hash index on the fields `name` and `age` and a separate hash index on the field `age`: +```java +@HashIndex(fields = {"name", "age"}) +public class Person { + private String name; + + @HashIndexed + private int age; +} +``` + +The `@Index` annotations can be used multiple times to create more than one index in this way. + +The following example creates a hash index on the fields `name` and `age` and a separate hash index on the fields `name` and `gender`: +```java +@HashIndex(fields = {"name", "age"}) +@HashIndex(fields = {"name", "gender"}) +public class Person { + private String name; + + private int age; + + private Gender gender +} +``` diff --git a/src/main/java/com/arangodb/springframework/config/AbstractArangoConfiguration.java b/src/main/java/com/arangodb/springframework/config/AbstractArangoConfiguration.java index 5ffc43c12..4f7fe26d9 100644 --- a/src/main/java/com/arangodb/springframework/config/AbstractArangoConfiguration.java +++ b/src/main/java/com/arangodb/springframework/config/AbstractArangoConfiguration.java @@ -39,8 +39,11 @@ import com.arangodb.springframework.core.ArangoOperations; import com.arangodb.springframework.core.convert.ArangoConverter; import com.arangodb.springframework.core.convert.ArangoCustomConversions; +import com.arangodb.springframework.core.convert.ArangoTypeMapper; import com.arangodb.springframework.core.convert.CustomConversions; +import com.arangodb.springframework.core.convert.DBEntityModule; import com.arangodb.springframework.core.convert.DefaultArangoConverter; +import com.arangodb.springframework.core.convert.DefaultArangoTypeMapper; import com.arangodb.springframework.core.convert.resolver.FromResolver; import com.arangodb.springframework.core.convert.resolver.RefResolver; import com.arangodb.springframework.core.convert.resolver.ReferenceResolver; @@ -55,19 +58,18 @@ /** * @author Mark Vollmary + * @author Christian Lechner * */ @Configuration public abstract class AbstractArangoConfiguration { - @Bean - public abstract ArangoDB.Builder arango(); + protected abstract ArangoDB.Builder arango(); - @Bean - public abstract String database(); + protected abstract String database(); private ArangoDB.Builder configure(final ArangoDB.Builder arango) { - return arango.registerModules(new VPackJdk8Module(), new VPackJodaModule()); + return arango.registerModules(new VPackJdk8Module(), new VPackJodaModule(), new DBEntityModule()); } @Bean @@ -85,7 +87,12 @@ public ArangoMappingContext arangoMappingContext() throws Exception { } @Bean - public CustomConversions customConversions() { + public ArangoConverter arangoConverter() throws Exception { + return new DefaultArangoConverter(arangoMappingContext(), customConversions(), resolverFactory(), + arangoTypeMapper()); + } + + protected CustomConversions customConversions() { return new ArangoCustomConversions(Collections.emptyList()); } @@ -101,9 +108,16 @@ protected FieldNamingStrategy fieldNamingStrategy() { return PropertyNameFieldNamingStrategy.INSTANCE; } - @Bean - public ArangoConverter arangoConverter() throws Exception { - return new DefaultArangoConverter(arangoMappingContext(), customConversions(), new ResolverFactory() { + protected String typeKey() { + return DefaultArangoTypeMapper.DEFAULT_TYPE_KEY; + } + + protected ArangoTypeMapper arangoTypeMapper() throws Exception { + return new DefaultArangoTypeMapper(typeKey(), arangoMappingContext()); + } + + protected ResolverFactory resolverFactory() { + return new ResolverFactory() { @SuppressWarnings("unchecked") @Override public Optional> getReferenceResolver(final A annotation) { @@ -135,7 +149,7 @@ public Optional> getRelationResolver( } return Optional.ofNullable(resolver); } - }); + }; } } diff --git a/src/main/java/com/arangodb/springframework/config/ArangoEntityClassScanner.java b/src/main/java/com/arangodb/springframework/config/ArangoEntityClassScanner.java index 0ba4e7257..8f5fb0af3 100644 --- a/src/main/java/com/arangodb/springframework/config/ArangoEntityClassScanner.java +++ b/src/main/java/com/arangodb/springframework/config/ArangoEntityClassScanner.java @@ -27,6 +27,7 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.data.annotation.TypeAlias; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -35,12 +36,16 @@ /** * @author Mark Vollmary + * @author Christian Lechner * */ public class ArangoEntityClassScanner { @SuppressWarnings("unchecked") private static final Class[] ENTITY_ANNOTATIONS = new Class[] { Document.class, Edge.class }; + + @SuppressWarnings("unchecked") + private static final Class[] ADDITIONAL_ANNOTATIONS = new Class[] { TypeAlias.class }; public static Set> scanForEntities(final String... basePackages) throws ClassNotFoundException { final Set> entities = new HashSet<>(); @@ -58,6 +63,9 @@ public static Set> scanForEntities(final String basePackage) throws Cla for (final Class annotationType : ENTITY_ANNOTATIONS) { componentProvider.addIncludeFilter(new AnnotationTypeFilter(annotationType)); } + for (final Class annotationType : ADDITIONAL_ANNOTATIONS) { + componentProvider.addIncludeFilter(new AnnotationTypeFilter(annotationType)); + } for (final BeanDefinition definition : componentProvider.findCandidateComponents(basePackage)) { entities.add(ClassUtils.forName(definition.getBeanClassName(), null)); } diff --git a/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java b/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java index f18cd86cc..ab2d0ab87 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java @@ -28,6 +28,7 @@ /** * @author Mark Vollmary + * @author Christian Lechner * */ public interface ArangoConverter extends ArangoEntityReader, ArangoEntityWriter { @@ -40,4 +41,6 @@ public interface ArangoConverter extends ArangoEntityReader, ArangoEntityWriter GenericConversionService getConversionService(); + ArangoTypeMapper getTypeMapper(); + } diff --git a/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java b/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java index b0b449ebc..f7585419d 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java +++ b/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java @@ -24,6 +24,7 @@ /** * @author Mark Vollmary + * @author Christian Lechner * */ public class ArangoCustomConversions extends CustomConversions { diff --git a/src/main/java/com/arangodb/springframework/core/convert/ArangoSimpleTypeConverters.java b/src/main/java/com/arangodb/springframework/core/convert/ArangoSimpleTypeConverters.java new file mode 100644 index 000000000..b26f1f9fe --- /dev/null +++ b/src/main/java/com/arangodb/springframework/core/convert/ArangoSimpleTypeConverters.java @@ -0,0 +1,106 @@ +/* + * 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.convert; + +import java.sql.Timestamp; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +import javax.xml.bind.DatatypeConverter; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; + +import com.arangodb.velocypack.internal.util.DateUtil; + +/** + * This class contains converters that are necessary to restore the original types defined in entities. Normally the + * Java driver would do this, but he has no type information available (since he is deserializing to + * {@link com.arangodb.springframework.core.convert.DBEntity} objects). + * + * @author Christian Lechner + * + */ +public class ArangoSimpleTypeConverters { + + public static Collection> getConvertersToRegister() { + final List> converters = new ArrayList<>(); + + converters.add(Base64StringToByteArrayConverter.INSTANCE); + converters.add(StringToDateConverter.INSTANCE); + converters.add(StringToSqlDateConverter.INSTANCE); + converters.add(StringToSqlTimestampConverter.INSTANCE); + + return converters; + } + + private static Date parse(final String source) { + try { + return DateUtil.parse(source); + } catch (final ParseException e) { + throw new IllegalArgumentException(e); + } + } + + @ReadingConverter + public static enum Base64StringToByteArrayConverter implements Converter { + INSTANCE; + + @Override + public byte[] convert(final String source) { + return DatatypeConverter.parseBase64Binary(source); + } + } + + @ReadingConverter + public static enum StringToDateConverter implements Converter { + INSTANCE; + + @Override + public Date convert(final String source) { + return parse(source); + } + } + + @ReadingConverter + public static enum StringToSqlDateConverter implements Converter { + INSTANCE; + + @Override + public java.sql.Date convert(final String source) { + return new java.sql.Date(parse(source).getTime()); + } + } + + @ReadingConverter + public static enum StringToSqlTimestampConverter implements Converter { + INSTANCE; + + @Override + public Timestamp convert(final String source) { + return new Timestamp(parse(source).getTime()); + } + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/ArangoTypeMapper.java b/src/main/java/com/arangodb/springframework/core/convert/ArangoTypeMapper.java new file mode 100644 index 000000000..942a9b2b4 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/core/convert/ArangoTypeMapper.java @@ -0,0 +1,33 @@ +/* + * 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.convert; + +import org.springframework.data.convert.TypeMapper; + +/** + * @author Christian Lechner + * + */ +public interface ArangoTypeMapper extends TypeMapper { + + boolean isTypeKey(String key); + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/DBEntityModule.java b/src/main/java/com/arangodb/springframework/core/convert/DBEntityModule.java new file mode 100644 index 000000000..907a3c653 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/core/convert/DBEntityModule.java @@ -0,0 +1,61 @@ +/* + * 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.convert; + +import java.util.Collection; +import java.util.Map; + +import com.arangodb.velocypack.VPackInstanceCreator; +import com.arangodb.velocypack.VPackModule; +import com.arangodb.velocypack.VPackSetupContext; + +/** + * @author Christian Lechner + * + */ +public class DBEntityModule implements VPackModule { + + @Override + public > void setup(C context) { + context.registerInstanceCreator(Map.class, new DBDocumentEntityInstantiator()) + .registerInstanceCreator(Collection.class, new DBCollectionEntityInstantiator()) + .registerDeserializer(DBEntity.class, new DBEntityDeserializer()); + } + + public static class DBDocumentEntityInstantiator implements VPackInstanceCreator> { + + @Override + public Map createInstance() { + return new DBDocumentEntity(); + } + + } + + public static class DBCollectionEntityInstantiator implements VPackInstanceCreator> { + + @Override + public Collection createInstance() { + return new DBCollectionEntity(); + } + + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java index 0ab508489..7ca102f85 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java @@ -46,6 +46,7 @@ import org.springframework.data.mapping.model.PropertyValueProvider; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; +import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import com.arangodb.entity.BaseDocument; @@ -57,6 +58,7 @@ /** * @author Mark Vollmary + * @author Christian Lechner * */ public class DefaultArangoConverter implements ArangoConverter { @@ -68,14 +70,16 @@ public class DefaultArangoConverter implements ArangoConverter { private final GenericConversionService conversionService; private final EntityInstantiators instantiators; private final ResolverFactory resolverFactory; + private final ArangoTypeMapper typeMapper; public DefaultArangoConverter( final MappingContext, ArangoPersistentProperty> context, - final CustomConversions conversions, final ResolverFactory resolverFactory) { + final CustomConversions conversions, final ResolverFactory resolverFactory, final ArangoTypeMapper typeMapper) { super(); this.context = context; this.conversions = conversions; this.resolverFactory = resolverFactory; + this.typeMapper = typeMapper; conversionService = new DefaultConversionService(); conversions.registerConvertersIn(conversionService); instantiators = new EntityInstantiators(); @@ -86,6 +90,11 @@ public MappingContext, ArangoPersistentPrope return context; } + @Override + public ArangoTypeMapper getTypeMapper() { + return typeMapper; + } + @SuppressWarnings("unchecked") @Override public R read(final Class type, final DBEntity source) { @@ -96,66 +105,68 @@ private Object read(final TypeInformation type, final DBEntity source) { if (source == null) { return null; } - if (conversions.hasCustomReadTarget(type.getType(), type.getType())) { - return conversionService.convert(source, type.getType()); + + final TypeInformation typeToUse = typeMapper.readType(source, type); + + if (conversions.hasCustomReadTarget(source.getClass(), typeToUse.getType())) { + return conversionService.convert(source, typeToUse.getType()); } - if (isMapType(type.getType()) && source instanceof DBDocumentEntity) { - return readMap(type, DBDocumentEntity.class.cast(source)); + + if (DBEntity.class.isAssignableFrom(typeToUse.getType())) { + return source; } - if (type.isCollectionLike() && source instanceof DBCollectionEntity) { - return readCollection(type, DBCollectionEntity.class.cast(source)); + + if (typeToUse.isMap()) { + return readMap(typeToUse, DBDocumentEntity.class.cast(source)); } + if (typeToUse.isCollectionLike()) { + return readCollection(typeToUse, DBCollectionEntity.class.cast(source)); + } + + // no type information available => stick to the given type of the source + if (typeToUse.equals(ClassTypeInformation.OBJECT)) { + if (source instanceof DBDocumentEntity) { + return readMap(ClassTypeInformation.MAP, DBDocumentEntity.class.cast(source)); + } else if (source instanceof DBCollectionEntity) { + return readCollection(ClassTypeInformation.LIST, DBCollectionEntity.class.cast(source)); + } + return source; + } + final Optional> entity = Optional - .ofNullable(context.getPersistentEntity(type.getType())); - return read(type, source, entity); + .ofNullable(context.getPersistentEntity(typeToUse.getType())); + return read(typeToUse, source, entity); } - @SuppressWarnings("unchecked") private Object readMap(final TypeInformation type, final DBDocumentEntity source) { - final Class keyType = type.getComponentType().getType(); - final TypeInformation valueType = type.getMapValueType(); + final Class keyType = getNonNullComponentType(type).getType(); + final TypeInformation valueType = getNonNullMapValueType(type); final Map map = CollectionFactory.createMap(type.getType(), keyType, source.size()); for (final Map.Entry entry : source.entrySet()) { - final Object key = conversionService.convert(entry.getKey(), keyType); + if (typeMapper.isTypeKey(entry.getKey())) { + continue; + } + final Object key = convertIfNecessary(entry.getKey(), keyType); final Object value = entry.getValue(); if (value instanceof DBEntity) { map.put(key, read(valueType, (DBEntity) value)); - } else if (value instanceof Map) { - map.put(key, read(valueType, new DBDocumentEntity((Map) value))); - } else if (value instanceof Collection) { - map.put(key, read(valueType, new DBCollectionEntity((Collection) value))); - } else if (isSimpleType(valueType.getType())) { - final Optional> customWriteTarget = Optional - .ofNullable(conversions.getCustomWriteTarget(valueType.getType())); - final Class targetType = customWriteTarget.orElseGet(() -> valueType.getType()); - map.put(key, conversionService.convert(value, targetType)); } else { - map.put(key, value); + map.put(key, convertIfNecessary(value, valueType.getType())); } } return map; } - @SuppressWarnings("unchecked") private Object readCollection(final TypeInformation type, final DBCollectionEntity source) { final Class collectionType = Collection.class.isAssignableFrom(type.getType()) ? type.getType() : List.class; - final TypeInformation componentType = getComponentType(type); + final TypeInformation componentType = getNonNullComponentType(type); final Collection entries = type.getType().isArray() ? new ArrayList<>() : CollectionFactory.createCollection(collectionType, componentType.getType(), source.size()); for (final Object entry : source) { if (entry instanceof DBEntity) { entries.add(read(componentType, (DBEntity) entry)); - } else if (entry instanceof Map) { - entries.add(read(componentType, new DBDocumentEntity((Map) entry))); - } else if (entry instanceof Collection) { - entries.add(read(componentType, new DBCollectionEntity((Collection) entry))); - } else if (isSimpleType(componentType.getType())) { - final Optional> customWriteTarget = Optional - .ofNullable(conversions.getCustomWriteTarget(componentType.getType())); - final Class targetType = customWriteTarget.orElseGet(() -> componentType.getType()); - entries.add(conversionService.convert(entry, targetType)); } else { - entries.add(entry); + entries.add(convertIfNecessary(entry, componentType.getType())); } } return entries; @@ -174,11 +185,15 @@ private Object read( conversionService); entity.doWithProperties((final ArangoPersistentProperty property) -> { - readProperty(source.get(_ID), accessor, source.get(property.getFieldName()), property); + if (!entity.isConstructorArgument(property)) { + readProperty(source.get(_ID), accessor, source.get(property.getFieldName()), property); + } }); entity.doWithAssociations((final Association association) -> { final ArangoPersistentProperty property = association.getInverse(); - readProperty(source.get(_ID), accessor, source.get(property.getFieldName()), property); + if (!entity.isConstructorArgument(property)) { + readProperty(source.get(_ID), accessor, source.get(property.getFieldName()), property); + } }); return instance; } @@ -216,7 +231,7 @@ private void readProperty( final ArangoPersistentProperty property) { final Optional referenceOrRelation = readReferenceOrRelation(parentId, source, property); accessor.setProperty(property, - referenceOrRelation.orElseGet(() -> (read(source, property.getTypeInformation())))); + referenceOrRelation.orElseGet(() -> read(source, property.getTypeInformation()))); } private Optional readReferenceOrRelation( @@ -259,7 +274,7 @@ private Optional readReference( "Collection of Type String expected for references but found type " + source.getClass()); } return Optional.ofNullable(resolver.resolveMultiple(ids, - getComponentType(property.getTypeInformation()).getType(), annotation)); + getNonNullComponentType(property.getTypeInformation()).getType(), annotation)); } else { if (!(source instanceof String)) { throw new MappingException( @@ -279,7 +294,7 @@ private Optional readRelation( return resolverFactory.getRelationResolver(annotation).flatMap(resolver -> { if (property.isCollectionLike() && parentId != null) { return Optional.of(resolver.resolveMultiple(parentId.toString(), - getComponentType(property.getTypeInformation()).getType(), annotation)); + getNonNullComponentType(property.getTypeInformation()).getType(), annotation)); } else if (source != null) { return Optional.of( resolver.resolveOne(source.toString(), property.getTypeInformation().getType(), annotation)); @@ -310,28 +325,39 @@ public void write(final Object source, final DBEntity sink) { if (source == null) { return; } - write(source, ClassTypeInformation.from(source.getClass()), sink); + + if (sink instanceof DBDocumentEntity + && conversions.hasCustomWriteTarget(source.getClass(), DBDocumentEntity.class)) { + final DBDocumentEntity result = conversionService.convert(source, DBDocumentEntity.class); + ((DBDocumentEntity) sink).putAll(result); + } + + final TypeInformation type = ClassTypeInformation.from(ClassUtils.getUserClass(source.getClass())); + final TypeInformation definedType = ClassTypeInformation.OBJECT; + + write(source, type, sink, definedType); } @SuppressWarnings("unchecked") - private void write(final Object source, final TypeInformation type, final DBEntity sink) { - if (isMapType(type.getType())) { - writeMap((Map) source, sink); + private void write( + final Object source, + final TypeInformation type, + final DBEntity sink, + final TypeInformation definedType) { + + if (type.isMap()) { + writeMap((Map) source, sink, definedType); return; } - if (isCollectionType(type.getType())) { - writeCollection(source, sink); + if (type.isCollectionLike()) { + writeCollection(source, sink, definedType); return; } - write(source, sink, Optional.ofNullable(context.getPersistentEntity(type))); + write(source, sink, context.getPersistentEntity(type)); + addTypeKeyIfNecessary(definedType, source, sink); } - private void write( - final Object source, - final DBEntity sink, - final Optional> entityC) { - final ArangoPersistentEntity entity = entityC.orElseThrow( - () -> new MappingException("No mapping metadata found for type " + source.getClass().getName())); + private void write(final Object source, final DBEntity sink, final ArangoPersistentEntity entity) { final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source); @@ -368,11 +394,11 @@ private void writeProperty(final Object source, final DBEntity sink, final Arang if (valueType.isCollectionLike()) { final Collection ids = new ArrayList<>(); for (final Object ref : createCollection(asCollection(source), property)) { - getId(ref, property).ifPresent(id -> ids.add(id)); + getId(ref).ifPresent(id -> ids.add(id)); } sink.put(fieldName, ids); } else { - getId(source, property).ifPresent(id -> sink.put(fieldName, id)); + getId(source).ifPresent(id -> sink.put(fieldName, id)); } return; } @@ -381,43 +407,43 @@ private void writeProperty(final Object source, final DBEntity sink, final Arang } if (property.getFrom().isPresent() || property.getTo().isPresent()) { if (!valueType.isCollectionLike()) { - getId(source, property).ifPresent(id -> sink.put(fieldName, id)); + getId(source).ifPresent(id -> sink.put(fieldName, id)); } return; } + if (conversions.isSimpleType(valueType.getType())) { + final Optional> customWriteTarget = Optional + .ofNullable(conversions.getCustomWriteTarget(source.getClass())); + final Class targetType = customWriteTarget.orElseGet(() -> valueType.getType()); + sink.put(fieldName, conversionService.convert(source, targetType)); + return; + } if (valueType.isCollectionLike()) { final DBEntity collection = new DBCollectionEntity(); - writeCollection(source, collection); + writeCollection(source, collection, property.getTypeInformation()); sink.put(fieldName, collection); return; } if (valueType.isMap()) { final DBEntity map = new DBDocumentEntity(); - writeMap((Map) source, map); + writeMap((Map) source, map, property.getTypeInformation()); sink.put(fieldName, map); return; } - final Optional> customWriteTarget = Optional - .ofNullable(conversions.getCustomWriteTarget(source.getClass())); - final Class targetType = customWriteTarget.orElseGet(() -> property.getTypeInformation().getType()); + final ArangoPersistentEntity persistentEntity = context.getPersistentEntity(valueType); final DBEntity document = new DBDocumentEntity(); - final Optional> persistentEntity = Optional - .ofNullable(context.getPersistentEntity(targetType)); - if (persistentEntity.isPresent()) { - write(source, document, persistentEntity); - sink.put(fieldName, document); - } else { - sink.put(fieldName, conversionService.convert(source, targetType)); - } + write(source, document, persistentEntity); + addTypeKeyIfNecessary(property.getTypeInformation(), source, document); + sink.put(fieldName, document); return; } - private void writeMap(final Map source, final DBEntity sink) { + private void writeMap(final Map source, final DBEntity sink, final TypeInformation definedType) { for (final Entry entry : source.entrySet()) { final Object key = entry.getKey(); - if (!conversions.isSimpleType(key.getClass())) { + if (!conversions.isSimpleType(key.getClass()) || key instanceof DBEntity) { throw new MappingException( - "Complexe type as Map key value is not allowed! fount type " + key.getClass()); + "Complex type " + key.getClass().getName() + " is not allowed as a map key!"); } final Object value = entry.getValue(); final Class valueType = value.getClass(); @@ -425,16 +451,16 @@ private void writeMap(final Map source, final DBEntity sink) { final Optional> customWriteTarget = Optional .ofNullable(conversions.getCustomWriteTarget(valueType)); final Class targetType = customWriteTarget.orElseGet(() -> valueType); - sink.put(key.toString(), conversionService.convert(value, targetType)); + sink.put(convertMapKey(key), conversionService.convert(value, targetType)); } else { final DBEntity entity = createDBEntity(valueType); - write(value, ClassTypeInformation.from(valueType), entity); - sink.put(key.toString(), entity); + write(value, ClassTypeInformation.from(valueType), entity, getNonNullMapValueType(definedType)); + sink.put(convertMapKey(key), entity); } } } - private void writeCollection(final Object source, final DBEntity sink) { + private void writeCollection(final Object source, final DBEntity sink, final TypeInformation definedType) { for (final Object entry : asCollection(source)) { final Class valueType = entry.getClass(); if (conversions.isSimpleType(valueType)) { @@ -444,31 +470,27 @@ private void writeCollection(final Object source, final DBEntity sink) { sink.add(conversionService.convert(entry, targetType)); } else { final DBEntity entity = createDBEntity(valueType); - write(entry, ClassTypeInformation.from(valueType), entity); + write(entry, ClassTypeInformation.from(valueType), entity, getNonNullComponentType(definedType)); sink.add(entity); } } } - private Optional getId(final Object source, final ArangoPersistentProperty property) { - return Optional.ofNullable(context.getPersistentEntity(property)).flatMap(entity -> getId(source, entity)); + private Optional getId(final Object source) { + return getId(source, context.getPersistentEntity(source.getClass())); } private Optional getId(final Object source, final ArangoPersistentEntity entity) { - return Optional.ofNullable(entity.getIdProperty()).map(p -> entity.getPropertyAccessor(source).getProperty(p)); + return Optional.ofNullable(entity.getIdentifierAccessor(source).getIdentifier()); } private Collection createCollection(final Collection source, final ArangoPersistentProperty property) { return source.stream() - .map(s -> conversionService.convert(s, getComponentType(property.getTypeInformation()).getType())) + .map( + s -> conversionService.convert(s, getNonNullComponentType(property.getTypeInformation()).getType())) .collect(Collectors.toList()); } - private TypeInformation getComponentType(final TypeInformation type) { - return Optional.ofNullable(type.getComponentType()) - .orElseThrow(() -> new MappingException("Can not determine collection component type")); - } - private static Collection asCollection(final Object source) { return (source instanceof Collection) ? Collection.class.cast(source) : source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source); @@ -514,4 +536,30 @@ private String determineDocumentKeyFromId(final String id) { return split[split.length - 1]; } + private void addTypeKeyIfNecessary(final TypeInformation definedType, final Object value, final DBEntity sink) { + final Class referenceType = definedType != null ? definedType.getType() : Object.class; + final Class valueType = ClassUtils.getUserClass(value.getClass()); + if (!valueType.equals(referenceType)) { + typeMapper.writeType(valueType, sink); + } + } + + private String convertMapKey(final Object key) { + if (key instanceof String) { + return (String) key; + } + final boolean hasCustomConverter = conversions.hasCustomWriteTarget(key.getClass(), String.class); + return hasCustomConverter ? conversionService.convert(key, String.class) : key.toString(); + } + + private TypeInformation getNonNullComponentType(final TypeInformation type) { + final TypeInformation compType = type.getComponentType(); + return compType != null ? compType : ClassTypeInformation.OBJECT; + } + + private TypeInformation getNonNullMapValueType(final TypeInformation type) { + final TypeInformation valueType = type.getMapValueType(); + return valueType != null ? valueType : ClassTypeInformation.OBJECT; + } + } diff --git a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoTypeMapper.java b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoTypeMapper.java new file mode 100644 index 000000000..8fff5f33a --- /dev/null +++ b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoTypeMapper.java @@ -0,0 +1,104 @@ +/* + * 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.convert; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.data.convert.DefaultTypeMapper; +import org.springframework.data.convert.SimpleTypeInformationMapper; +import org.springframework.data.convert.TypeAliasAccessor; +import org.springframework.data.convert.TypeInformationMapper; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.context.MappingContext; + +/** + * @author Christian Lechner + * + */ +public class DefaultArangoTypeMapper extends DefaultTypeMapper implements ArangoTypeMapper { + + public static final String DEFAULT_TYPE_KEY = "_class"; + + private final String typeKey; + + public DefaultArangoTypeMapper() { + this(DEFAULT_TYPE_KEY); + } + + public DefaultArangoTypeMapper(final String typeKey) { + this(typeKey, Arrays.asList(new SimpleTypeInformationMapper())); + } + + public DefaultArangoTypeMapper(final String typeKey, + final MappingContext, ?> mappingContext) { + this(typeKey, new DocumentTypeAliasAccessor(typeKey), mappingContext, + Arrays.asList(new SimpleTypeInformationMapper())); + } + + public DefaultArangoTypeMapper(final String typeKey, final List mappers) { + this(typeKey, new DocumentTypeAliasAccessor(typeKey), null, mappers); + } + + private DefaultArangoTypeMapper(final String typeKey, final TypeAliasAccessor accessor, + final MappingContext, ?> mappingContext, + final List mappers) { + + super(accessor, mappingContext, mappers); + this.typeKey = typeKey; + } + + @Override + public boolean isTypeKey(final String key) { + return typeKey == null ? false : typeKey.equals(key); + } + + public static final class DocumentTypeAliasAccessor implements TypeAliasAccessor { + + private final String typeKey; + + public DocumentTypeAliasAccessor(final String typeKey) { + this.typeKey = typeKey; + } + + @Override + public Object readAliasFrom(final DBEntity source) { + if (source instanceof DBCollectionEntity) { + return null; + } + + if (source instanceof DBDocumentEntity) { + return source.get(this.typeKey); + } + + throw new IllegalArgumentException("Cannot read alias from " + source.getClass()); + } + + @Override + public void writeTypeTo(final DBEntity sink, final Object alias) { + if (this.typeKey != null && sink instanceof DBDocumentEntity) { + sink.put(this.typeKey, alias); + } + } + + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java b/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java index ce1a1b48e..14a499273 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java +++ b/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java @@ -27,28 +27,22 @@ import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.List; import org.springframework.core.convert.converter.Converter; -import org.springframework.util.ClassUtils; import com.arangodb.ArangoDBException; import com.arangodb.velocypack.internal.util.DateUtil; /** * @author Mark Vollmary + * @author Christian Lechner * */ public class TimeStringConverters { - private static final boolean JODA_TIME_IS_PRESENT = ClassUtils.isPresent("org.joda.time.LocalDate", null); - public static Collection> getConvertersToRegister() { - if (!JODA_TIME_IS_PRESENT) { - return Collections.emptySet(); - } final List> converters = new ArrayList<>(); converters.add(InstantToStringConverter.INSTANCE); converters.add(LocalDateToStringConverter.INSTANCE); diff --git a/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java b/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java new file mode 100644 index 000000000..921b191ea --- /dev/null +++ b/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java @@ -0,0 +1,97 @@ +/* + * 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.mapping; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.springframework.data.mapping.model.SimpleTypeHolder; + +import com.arangodb.springframework.core.convert.DBEntity; + +/** + * This class contains all types that are directly supported by the Java driver (through java-velocypack). + * + * @author Christian Lechner + * + */ +public abstract class ArangoSimpleTypes { + + private static final Set> ARANGO_SIMPLE_TYPES; + + static { + final Set> simpleTypes = new HashSet>(); + + // com.arangodb.springframework.* + simpleTypes.add(DBEntity.class); + + // primitives + simpleTypes.add(byte.class); + simpleTypes.add(char.class); + simpleTypes.add(boolean.class); + simpleTypes.add(short.class); + simpleTypes.add(int.class); + simpleTypes.add(long.class); + simpleTypes.add(float.class); + simpleTypes.add(double.class); + + // java.lang.* + simpleTypes.add(Byte.class); + simpleTypes.add(Character.class); + simpleTypes.add(Boolean.class); + simpleTypes.add(Short.class); + simpleTypes.add(Integer.class); + simpleTypes.add(Long.class); + simpleTypes.add(Float.class); + simpleTypes.add(Double.class); + simpleTypes.add(Number.class); + simpleTypes.add(String.class); + + // arrays + simpleTypes.add(byte[].class); + + // java.math.* + simpleTypes.add(BigInteger.class); + simpleTypes.add(BigDecimal.class); + + // java.util.* + simpleTypes.add(UUID.class); + simpleTypes.add(Date.class); + + // java.sql.* + simpleTypes.add(java.sql.Date.class); + simpleTypes.add(Timestamp.class); + + ARANGO_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes); + } + + public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(ARANGO_SIMPLE_TYPES, false); + + private ArangoSimpleTypes() { + } + +} \ 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 76b137406..8c15a81e4 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -69,7 +69,6 @@ import com.arangodb.springframework.core.convert.DBCollectionEntity; import com.arangodb.springframework.core.convert.DBDocumentEntity; import com.arangodb.springframework.core.convert.DBEntity; -import com.arangodb.springframework.core.convert.DBEntityDeserializer; import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; import com.arangodb.springframework.core.template.DefaultUserOperation.CollectionCallback; @@ -102,9 +101,8 @@ public ArangoTemplate(final ArangoDB.Builder arango, final String database, fina public ArangoTemplate(final ArangoDB.Builder arango, final String database, final ArangoConverter converter, final PersistenceExceptionTranslator exceptionTranslator) { super(); - this.arango = arango.registerDeserializer(DBEntity.class, new DBEntityDeserializer()).build() - ._setCursorInitializer( - new com.arangodb.springframework.core.template.ArangoCursorInitializer(converter)); + this.arango = arango.build()._setCursorInitializer( + new com.arangodb.springframework.core.template.ArangoCursorInitializer(converter)); this.databaseName = database; this.converter = converter; this.exceptionTranslator = exceptionTranslator; diff --git a/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java b/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java index 44c6e09ab..a111fc7af 100644 --- a/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java +++ b/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java @@ -28,12 +28,17 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.stream.Collectors; import org.junit.Test; @@ -60,6 +65,7 @@ /** * @author Mark Vollmary + * @author Christian Lechner * */ @RunWith(SpringJUnit4ClassRunner.class) @@ -843,9 +849,9 @@ public void constructorWithRelationsLazyParams() { public static class ConstructorWithFromParamsTestEntity extends BasicTestEntity { @From - private final Collection value; + private final Collection value; - public ConstructorWithFromParamsTestEntity(final Collection value) { + public ConstructorWithFromParamsTestEntity(final Collection value) { super(); this.value = value; } @@ -857,8 +863,8 @@ public void constructorWithFromParams() { template.insert(entity); final BasicTestEntity to = new BasicTestEntity(); template.insert(to); - final BasicEdgeTestEntity edge1 = new BasicEdgeTestEntity(entity, to); - final BasicEdgeTestEntity edge2 = new BasicEdgeTestEntity(entity, to); + final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(entity, to); + final BasicEdgeLazyTestEntity edge2 = new BasicEdgeLazyTestEntity(entity, to); template.insert(edge1); template.insert(edge2); final ConstructorWithFromParamsTestEntity document = template @@ -896,9 +902,9 @@ public void constructorWithFromLazyParams() { public static class ConstructorWithToParamsTestEntity extends BasicTestEntity { @To - private final Collection value; + private final Collection value; - public ConstructorWithToParamsTestEntity(final Collection value) { + public ConstructorWithToParamsTestEntity(final Collection value) { super(); this.value = value; } @@ -910,8 +916,8 @@ public void constructorWithToParams() { template.insert(entity); final BasicTestEntity from = new BasicTestEntity(); template.insert(from); - final BasicEdgeTestEntity edge1 = new BasicEdgeTestEntity(from, entity); - final BasicEdgeTestEntity edge2 = new BasicEdgeTestEntity(from, entity); + final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(from, entity); + final BasicEdgeLazyTestEntity edge2 = new BasicEdgeLazyTestEntity(from, entity); template.insert(edge1); template.insert(edge2); final ConstructorWithToParamsTestEntity document = template @@ -1047,4 +1053,365 @@ public void timeMapping() { assertThat(document.value3, is(entity.value3)); } + public static class SimpleBasicChildTestEntity extends BasicTestEntity { + private String field; + } + + public static class ComplexBasicChildTestEntity extends BasicTestEntity { + private BasicTestEntity nestedEntity; + } + + public static class PropertyInheritanceTestEntity extends BasicTestEntity { + private BasicTestEntity value; + } + + @Test + public void simplePropertyInheritanceMapping() { + final SimpleBasicChildTestEntity child = new SimpleBasicChildTestEntity(); + child.field = "value"; + final PropertyInheritanceTestEntity entity = new PropertyInheritanceTestEntity(); + entity.value = child; + template.insert(entity); + final PropertyInheritanceTestEntity document = template + .find(entity.getId(), PropertyInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(SimpleBasicChildTestEntity.class))); + assertThat(((SimpleBasicChildTestEntity) document.value).field, is(child.field)); + } + + @Test + public void complexPropertyInheritanceMapping() { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = "value"; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + final PropertyInheritanceTestEntity entity = new PropertyInheritanceTestEntity(); + entity.value = child; + template.insert(entity); + final PropertyInheritanceTestEntity document = template + .find(entity.getId(), PropertyInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); + ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; + assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; + assertThat(simpleDocument.field, is(innerChild.field)); + } + + public static class ListInheritanceTestEntity extends BasicTestEntity { + private List value; + } + + @Test + public void simpleListInheritanceMapping() { + final List list = new ArrayList<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity child = new SimpleBasicChildTestEntity(); + child.field = value; + list.add(child); + } + final ListInheritanceTestEntity entity = new ListInheritanceTestEntity(); + entity.value = list; + template.insert(entity); + final ListInheritanceTestEntity document = template.find(entity.getId(), ListInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(List.class))); + for (BasicTestEntity elem : document.value) { + assertThat(elem, is(instanceOf(SimpleBasicChildTestEntity.class))); + assertThat(((SimpleBasicChildTestEntity) elem).field, is(value)); + } + } + + @Test + public void complexListInheritanceMapping() { + final List list = new ArrayList<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = value; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + list.add(child); + } + final ListInheritanceTestEntity entity = new ListInheritanceTestEntity(); + entity.value = list; + template.insert(entity); + final ListInheritanceTestEntity document = template.find(entity.getId(), ListInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(List.class))); + for (BasicTestEntity elem : document.value) { + assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); + ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; + assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + assertThat(simpleElem.field, is(value)); + } + } + + @SuppressWarnings("rawtypes") + public static class UntypedListInheritanceTestEntity extends BasicTestEntity { + private List value; + } + + @Test + public void untypedListInheritanceMapping() { + final List list = new ArrayList<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = value; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + list.add(child); + } + final UntypedListInheritanceTestEntity entity = new UntypedListInheritanceTestEntity(); + entity.value = list; + template.insert(entity); + final UntypedListInheritanceTestEntity document = template + .find(entity.getId(), UntypedListInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(List.class))); + for (Object elem : document.value) { + assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); + ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; + assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + assertThat(simpleElem.field, is(value)); + } + } + + public static class MapInheritanceTestEntity extends BasicTestEntity { + private Map value; + } + + @Test + public void simpleMapInheritanceMapping() { + final Map map = new HashMap<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity child = new SimpleBasicChildTestEntity(); + child.field = value; + map.put(String.valueOf(i), child); + } + final MapInheritanceTestEntity entity = new MapInheritanceTestEntity(); + entity.value = map; + template.insert(entity); + final MapInheritanceTestEntity document = template.find(entity.getId(), MapInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(Map.class))); + for (Map.Entry entry : document.value.entrySet()) { + assertThat(entry.getValue(), is(instanceOf(SimpleBasicChildTestEntity.class))); + assertThat(((SimpleBasicChildTestEntity) entry.getValue()).field, is(value)); + } + } + + @Test + public void complexMapInheritanceMapping() { + final Map map = new HashMap<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = value; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + map.put(String.valueOf(i), child); + } + final MapInheritanceTestEntity entity = new MapInheritanceTestEntity(); + entity.value = map; + template.insert(entity); + final MapInheritanceTestEntity document = template.find(entity.getId(), MapInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(Map.class))); + for (Map.Entry entry : document.value.entrySet()) { + assertThat(entry.getValue(), is(instanceOf(ComplexBasicChildTestEntity.class))); + ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) entry.getValue(); + assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + assertThat(simpleElem.field, is(value)); + } + } + + @SuppressWarnings("rawtypes") + public static class UntypedMapInheritanceTestEntity extends BasicTestEntity { + private Map value; + } + + @SuppressWarnings("rawtypes") + @Test + public void untypedMapInheritanceMapping() { + final Map map = new HashMap<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = value; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + map.put(String.valueOf(i), child); + } + final UntypedMapInheritanceTestEntity entity = new UntypedMapInheritanceTestEntity(); + entity.value = map; + template.insert(entity); + final UntypedMapInheritanceTestEntity document = template + .find(entity.getId(), UntypedMapInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(Map.class))); + for (Object entry : document.value.entrySet()) { + final Object val = ((Map.Entry) entry).getValue(); + assertThat(val, is(instanceOf(ComplexBasicChildTestEntity.class))); + ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) val; + assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + assertThat(simpleElem.field, is(value)); + } + } + + public static class ConstructorWithPropertyInheritanceTestEntity extends BasicTestEntity { + private final BasicTestEntity value; + + public ConstructorWithPropertyInheritanceTestEntity(final BasicTestEntity value) { + this.value = value; + } + } + + @Test + public void constructorPropertyInheritanceMapping() { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = "value"; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + final ConstructorWithPropertyInheritanceTestEntity entity = new ConstructorWithPropertyInheritanceTestEntity( + child); + template.insert(entity); + final ConstructorWithPropertyInheritanceTestEntity document = template + .find(entity.getId(), ConstructorWithPropertyInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); + ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; + assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; + assertThat(simpleDocument.field, is(innerChild.field)); + } + + public static class ListInMapInheritanceTestEntity extends BasicTestEntity { + private Map> value; + } + + @Test + public void listInMapInheritanceMapping() { + final Map> map = new HashMap<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + List list = new ArrayList<>(); + map.put(String.valueOf(i), list); + for (int j = 0; j < 3; ++j) { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = value; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + list.add(child); + } + } + final ListInMapInheritanceTestEntity entity = new ListInMapInheritanceTestEntity(); + entity.value = map; + template.insert(entity); + final ListInMapInheritanceTestEntity document = template + .find(entity.getId(), ListInMapInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(Map.class))); + for (Map.Entry> entry : document.value.entrySet()) { + assertThat(entry.getValue(), is(instanceOf(List.class))); + for (BasicTestEntity elem : entry.getValue()) { + assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); + ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; + assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + assertThat(simpleElem.field, is(value)); + } + } + } + + public static class PropertyRefInheritanceTestEntity extends BasicTestEntity { + @Ref + private BasicTestEntity value; + } + + @Test + public void propertyRefInheritanceMapping() { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = "value"; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + final PropertyRefInheritanceTestEntity entity = new PropertyRefInheritanceTestEntity(); + entity.value = child; + template.insert(child); + template.insert(entity); + final PropertyRefInheritanceTestEntity document = template + .find(entity.getId(), PropertyRefInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); + ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; + assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; + assertThat(simpleDocument.field, is(innerChild.field)); + } + + public class SimpleTypesTestEntity extends BasicTestEntity { + private String stringValue; + private Boolean boolValue; + private int intValue; + private Long longValue; + private Short shortValue; + private Float floatValue; + private Double doubleValue; + private Character charValue; + private Byte byteValue; + private BigInteger bigIntValue; + private BigDecimal bigDecValue; + private UUID uuidValue; + private Date dateValue; + private java.sql.Date sqlDateValue; + private Timestamp timestampValue; + private byte[] byteArray; + } + + @Test + public void simpleTypesMapping() { + SimpleTypesTestEntity entity = new SimpleTypesTestEntity(); + entity.stringValue = "hello world"; + entity.boolValue = true; + entity.intValue = 123456; + entity.longValue = 1234567890123456789l; + entity.shortValue = 1234; + entity.floatValue = 1.234567890f; + entity.doubleValue = 1.2345678901234567890; + entity.charValue = 'a'; + entity.byteValue = 'z'; + entity.bigIntValue = new BigInteger("123456789"); + entity.bigDecValue = new BigDecimal("1.23456789"); + entity.uuidValue = UUID.randomUUID(); + entity.dateValue = new Date(); + entity.sqlDateValue = new java.sql.Date(new Date().getTime()); + entity.timestampValue = new Timestamp(new Date().getTime()); + entity.byteArray = new byte[] { 'a', 'b', 'c', 'x', 'y', 'z' }; + template.insert(entity); + SimpleTypesTestEntity document = template.find(entity.getId(), SimpleTypesTestEntity.class).get(); + assertThat(entity.stringValue, is(document.stringValue)); + assertThat(entity.boolValue, is(document.boolValue)); + assertThat(entity.intValue, is(document.intValue)); + assertThat(entity.longValue, is(document.longValue)); + assertThat(entity.shortValue, is(document.shortValue)); + assertThat(entity.floatValue, is(document.floatValue)); + assertThat(entity.doubleValue, is(document.doubleValue)); + assertThat(entity.charValue, is(document.charValue)); + assertThat(entity.byteValue, is(document.byteValue)); + assertThat(entity.bigIntValue, is(document.bigIntValue)); + assertThat(entity.bigDecValue, is(document.bigDecValue)); + assertThat(entity.uuidValue, is(document.uuidValue)); + assertThat(entity.dateValue, is(document.dateValue)); + assertThat(entity.sqlDateValue, is(document.sqlDateValue)); + assertThat(entity.timestampValue, is(document.timestampValue)); + assertThat(entity.byteArray, is(document.byteArray)); + } + } From 0a63a903d37b206c1a0135880d17f66e3141e3e5 Mon Sep 17 00:00:00 2001 From: Christian Lechner <6638938+christian-lechner@users.noreply.github.com> Date: Wed, 18 Apr 2018 14:23:56 +0200 Subject: [PATCH 19/94] small refactorings for the converter (#37) --- .../core/convert/DefaultArangoConverter.java | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java index 7ca102f85..1b70c7198 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java @@ -133,8 +133,7 @@ private Object read(final TypeInformation type, final DBEntity source) { return source; } - final Optional> entity = Optional - .ofNullable(context.getPersistentEntity(typeToUse.getType())); + final ArangoPersistentEntity entity = context.getRequiredPersistentEntity(typeToUse.getType()); return read(typeToUse, source, entity); } @@ -172,12 +171,7 @@ private Object readCollection(final TypeInformation type, final DBCollectionE return entries; } - private Object read( - final TypeInformation type, - final DBEntity source, - final Optional> persistentEntity) { - final ArangoPersistentEntity entity = persistentEntity.orElseThrow( - () -> new MappingException("No mapping metadata found for type " + type.getType().getName())); + private Object read(final TypeInformation type, final DBEntity source, final ArangoPersistentEntity entity) { final EntityInstantiator instantiatorFor = instantiators.getInstantiatorFor(entity); final ParameterValueProvider provider = getParameterProvider(entity, source); final Object instance = instantiatorFor.createInstance(entity, provider); @@ -311,11 +305,8 @@ private T read(final Object source, final TypeInformation type) { if (conversions.hasCustomReadTarget(source.getClass(), type.getType())) { return (T) conversionService.convert(source, type.getType()); } - if (isMapType(source.getClass())) { - return (T) read(type, new DBDocumentEntity((Map) source)); - } - if (isCollectionType(source.getClass())) { - return (T) readCollection(type, new DBCollectionEntity((Collection) source)); + if (source instanceof DBEntity) { + return (T) read(type, DBEntity.class.cast(source)); } return (T) source; } From 96d96f6f47980c05bfacaa604420ed8137b783e8 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Wed, 18 Apr 2018 16:17:18 +0200 Subject: [PATCH 20/94] Fix missing deserialization of return types of @Query methods(issue #21) --- .../repository/query/ArangoAqlQuery.java | 31 +++++++++++++++++++ .../repository/CustomerRepository.java | 4 ++- .../repository/query/ArangoAqlQueryTest.java | 5 +++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java index 9259b4889..4a48170c8 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java @@ -22,14 +22,18 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.Arrays; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -58,6 +62,7 @@ import com.arangodb.springframework.core.ArangoOperations; import com.arangodb.springframework.core.mapping.ArangoMappingContext; import com.arangodb.springframework.repository.query.derived.DerivedQueryCreator; +import com.arangodb.velocypack.VPackSlice; /** * Implements execute(Object[]) method which is called every time a user-defined AQL or derived method is called @@ -75,6 +80,32 @@ public class ArangoAqlQuery implements RepositoryQuery { DESERIALIZABLE_TYPES.add(Map.class); DESERIALIZABLE_TYPES.add(BaseDocument.class); DESERIALIZABLE_TYPES.add(BaseEdgeDocument.class); + DESERIALIZABLE_TYPES.add(String.class); + DESERIALIZABLE_TYPES.add(Boolean.class); + DESERIALIZABLE_TYPES.add(boolean.class); + DESERIALIZABLE_TYPES.add(Integer.class); + DESERIALIZABLE_TYPES.add(int.class); + DESERIALIZABLE_TYPES.add(Long.class); + DESERIALIZABLE_TYPES.add(long.class); + DESERIALIZABLE_TYPES.add(Short.class); + DESERIALIZABLE_TYPES.add(short.class); + DESERIALIZABLE_TYPES.add(Double.class); + DESERIALIZABLE_TYPES.add(double.class); + DESERIALIZABLE_TYPES.add(Float.class); + DESERIALIZABLE_TYPES.add(float.class); + DESERIALIZABLE_TYPES.add(BigInteger.class); + DESERIALIZABLE_TYPES.add(BigDecimal.class); + DESERIALIZABLE_TYPES.add(Number.class); + DESERIALIZABLE_TYPES.add(Character.class); + DESERIALIZABLE_TYPES.add(char.class); + DESERIALIZABLE_TYPES.add(Date.class); + DESERIALIZABLE_TYPES.add(java.sql.Date.class); + DESERIALIZABLE_TYPES.add(java.sql.Timestamp.class); + DESERIALIZABLE_TYPES.add(VPackSlice.class); + DESERIALIZABLE_TYPES.add(UUID.class); + DESERIALIZABLE_TYPES.add(new byte[] {}.getClass()); + DESERIALIZABLE_TYPES.add(Byte.class); + DESERIALIZABLE_TYPES.add(byte.class); } private static final Logger LOGGER = LoggerFactory.getLogger(ArangoAqlQuery.class); diff --git a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java index 3db5a17d0..9f8227bed 100644 --- a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java +++ b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java @@ -26,7 +26,6 @@ import com.arangodb.springframework.annotation.Param; import com.arangodb.springframework.annotation.Query; import com.arangodb.springframework.annotation.QueryOptions; -import com.arangodb.springframework.repository.ArangoRepository; import com.arangodb.springframework.repository.query.derived.geo.Ring; import com.arangodb.springframework.testdata.Customer; @@ -198,4 +197,7 @@ List findByNestedCustomersNestedCustomerShoppingCartProductsLocationWi List getByOwnsName(String name); List getByOwnsContainsName(String name); + + @Query("RETURN COUNT(@@collection)") + long queryCount(@Param("@collection") Class collection); } diff --git a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java index 483098fc0..bbba8c2fb 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java @@ -176,4 +176,9 @@ public void findManyBySurnameTest() { assertTrue(equals(retrieved, toBeRetrieved, cmp, eq, false)); } + + @Test + public void queryCount() { + assertEquals(repository.queryCount(Customer.class), 0L); + } } From 80b9d3da9bb55e851b84adb2f99efa91af3a1874 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 19 Apr 2018 13:30:43 +0200 Subject: [PATCH 21/94] Update java driver 4.4.0, velocypack jdk8 1.1.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index deeafc0b4..94fb25f8a 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 1.1.3 1.3 4.12 - 4.3.4 + 4.4.0 4.3.13.RELEASE ${spring.version} ${spring.version} @@ -283,7 +283,7 @@ com.arangodb velocypack-module-jdk8 - 1.0.2 + 1.1.0 com.arangodb From e19e5275a4e41f874788396bbc692e866cc0c1e5 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 19 Apr 2018 14:11:12 +0200 Subject: [PATCH 22/94] Fix handling of java.time in converters --- ChangeLog | 10 ++- .../core/convert/TimeStringConverters.java | 70 +++++++++++++------ 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1b494ba80..90c472a40 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,12 @@ -v1.0.2 (2018-03-23) +v1.1.0 (2018-04-20) +--------------------------- +* added DataIntegrityViolationException to ExceptionTranslator +* fixed race conditions in ArangoTemplate when creating database and collections (issue #35) +* added type mapper implementation & custom conversions extension (issue #33) +* fixed missing deserialization of return types of @Query methods(issue #21) +* fixed handling of java.time in converters + +v2.0.3 (2018-03-23) --------------------------- * fixed missing WITH information in derived query diff --git a/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java b/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java index 14a499273..17cb852e6 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java +++ b/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java @@ -20,20 +20,18 @@ package com.arangodb.springframework.core.convert; -import java.text.ParseException; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.ZoneId; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collection; -import java.util.Date; import java.util.List; import org.springframework.core.convert.converter.Converter; -import com.arangodb.ArangoDBException; -import com.arangodb.velocypack.internal.util.DateUtil; +import com.arangodb.velocypack.module.jdk8.internal.util.JavaTimeUtil; /** * @author Mark Vollmary @@ -47,27 +45,23 @@ public class TimeStringConverters { converters.add(InstantToStringConverter.INSTANCE); converters.add(LocalDateToStringConverter.INSTANCE); converters.add(LocalDateTimeToStringConverter.INSTANCE); + converters.add(OffsetDateTimeToStringConverter.INSTANCE); + converters.add(ZonedDateTimeToStringConverter.INSTANCE); converters.add(StringToInstantConverter.INSTANCE); converters.add(StringToLocalDateConverter.INSTANCE); converters.add(StringToLocalDateTimeConverter.INSTANCE); + converters.add(StringToOffsetDateTimeConverter.INSTANCE); + converters.add(StringToZonedDateTimeConverter.INSTANCE); return converters; } - private static Date parse(final String source) { - try { - return DateUtil.parse(source); - } catch (final ParseException e) { - throw new ArangoDBException(e); - } - } - public static enum InstantToStringConverter implements Converter { INSTANCE; @Override public String convert(final Instant source) { - return source == null ? null : DateUtil.format(Date.from(source)); + return source == null ? null : JavaTimeUtil.format(source); } } @@ -76,8 +70,7 @@ public static enum LocalDateToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(final OffsetDateTime source) { + return source == null ? null : JavaTimeUtil.format(source); + } + } + + public static enum ZonedDateTimeToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(final ZonedDateTime source) { + return source == null ? null : JavaTimeUtil.format(source); } } @@ -96,7 +106,7 @@ public static enum StringToInstantConverter implements Converter { + INSTANCE; + + @Override + public OffsetDateTime convert(final String source) { + return source == null ? null : JavaTimeUtil.parseOffsetDateTime(source); + } + } + + public static enum StringToZonedDateTimeConverter implements Converter { + INSTANCE; + + @Override + public ZonedDateTime convert(final String source) { + return source == null ? null : JavaTimeUtil.parseZonedDateTime(source); } } From f7dd076bd8ec9d46bf168c3f59eba95b785beeb3 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 19 Apr 2018 14:12:18 +0200 Subject: [PATCH 23/94] Fix changelog --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 90c472a40..210383c10 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,7 +4,7 @@ v1.1.0 (2018-04-20) * fixed race conditions in ArangoTemplate when creating database and collections (issue #35) * added type mapper implementation & custom conversions extension (issue #33) * fixed missing deserialization of return types of @Query methods(issue #21) -* fixed handling of java.time in converters +* fixed handling of java.time in converters (issue #36, #24, #25) v2.0.3 (2018-03-23) --------------------------- From 5333a1a1276963090509c4e122099bb4c28faeed Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 20 Apr 2018 10:59:51 +0200 Subject: [PATCH 24/94] Fix handling of org.joda.time in converters (issue #36) --- ChangeLog | 1 + pom.xml | 2 +- .../core/convert/ArangoCustomConversions.java | 26 +++++++++++++---- .../core/convert/DefaultArangoConverter.java | 2 +- .../convert/JodaTimeStringConverters.java | 29 ++++++------------- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/ChangeLog b/ChangeLog index 210383c10..4c274e9d7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,7 @@ v1.1.0 (2018-04-20) * added type mapper implementation & custom conversions extension (issue #33) * fixed missing deserialization of return types of @Query methods(issue #21) * fixed handling of java.time in converters (issue #36, #24, #25) +* fixed handling of org.joda.time in converters (issue #36) v2.0.3 (2018-03-23) --------------------------- diff --git a/pom.xml b/pom.xml index 94fb25f8a..a1de69cd8 100644 --- a/pom.xml +++ b/pom.xml @@ -288,7 +288,7 @@ com.arangodb velocypack-module-joda - 1.0.0 + 1.1.0 diff --git a/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java b/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java index f7585419d..697a9ca75 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java +++ b/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java @@ -20,7 +20,11 @@ package com.arangodb.springframework.core.convert; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; + +import org.springframework.core.convert.converter.Converter; /** * @author Mark Vollmary @@ -29,15 +33,25 @@ */ public class ArangoCustomConversions extends CustomConversions { - // private static final StoreConversions STORE_CONVERSIONS; + private static final Collection STORE_CONVERSIONS; - // static { - // final Collection converters = new ArrayList<>(); - // STORE_CONVERSIONS = StoreConversions.of(SimpleTypeHolder.DEFAULT, converters); - // } + static { + final Collection> storeConverters = new ArrayList<>(); + storeConverters.addAll(TimeStringConverters.getConvertersToRegister()); + storeConverters.addAll(JodaTimeStringConverters.getConvertersToRegister()); + storeConverters.addAll(ArangoSimpleTypeConverters.getConvertersToRegister()); + STORE_CONVERSIONS = Collections.unmodifiableCollection(storeConverters); + } public ArangoCustomConversions(final Collection converters) { - super(converters); + super(merge(converters)); + } + + private static Collection merge(final Collection converters) { + final Collection mergedList = new ArrayList<>(); + mergedList.addAll(STORE_CONVERSIONS); + mergedList.addAll(converters); + return mergedList; } } diff --git a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java index 1b70c7198..3015139db 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java @@ -133,7 +133,7 @@ private Object read(final TypeInformation type, final DBEntity source) { return source; } - final ArangoPersistentEntity entity = context.getRequiredPersistentEntity(typeToUse.getType()); + final ArangoPersistentEntity entity = context.getPersistentEntity(typeToUse.getType()); return read(typeToUse, source, entity); } diff --git a/src/main/java/com/arangodb/springframework/core/convert/JodaTimeStringConverters.java b/src/main/java/com/arangodb/springframework/core/convert/JodaTimeStringConverters.java index 05576e43c..899b16b72 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/JodaTimeStringConverters.java +++ b/src/main/java/com/arangodb/springframework/core/convert/JodaTimeStringConverters.java @@ -20,11 +20,9 @@ package com.arangodb.springframework.core.convert; -import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Date; import java.util.List; import org.joda.time.DateTime; @@ -34,8 +32,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.util.ClassUtils; -import com.arangodb.ArangoDBException; -import com.arangodb.velocypack.internal.util.DateUtil; +import com.arangodb.velocypack.module.joda.internal.util.JodaTimeUtil; /** * @author Mark Vollmary @@ -62,20 +59,12 @@ public class JodaTimeStringConverters { return converters; } - private static Date parse(final String source) { - try { - return DateUtil.parse(source); - } catch (final ParseException e) { - throw new ArangoDBException(e); - } - } - public static enum InstantToStringConverter implements Converter { INSTANCE; @Override public String convert(final Instant source) { - return source == null ? null : DateUtil.format(source.toDate()); + return source == null ? null : JodaTimeUtil.format(source); } } @@ -84,7 +73,7 @@ public static enum DateTimeToStringConverter implements Converter Date: Fri, 20 Apr 2018 11:14:54 +0200 Subject: [PATCH 25/94] Fix missing deserialization of return types of @Query methods(issue #21) --- .../repository/query/ArangoAqlQuery.java | 10 ++++++++++ .../springframework/repository/CustomerRepository.java | 4 ++++ .../repository/query/ArangoAqlQueryTest.java | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java index 4a48170c8..73bfee61e 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java @@ -24,6 +24,11 @@ import java.lang.reflect.Method; import java.math.BigDecimal; import java.math.BigInteger; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collection; import java.util.Date; @@ -106,6 +111,11 @@ public class ArangoAqlQuery implements RepositoryQuery { DESERIALIZABLE_TYPES.add(new byte[] {}.getClass()); DESERIALIZABLE_TYPES.add(Byte.class); DESERIALIZABLE_TYPES.add(byte.class); + DESERIALIZABLE_TYPES.add(Instant.class); + DESERIALIZABLE_TYPES.add(LocalDate.class); + DESERIALIZABLE_TYPES.add(LocalDateTime.class); + DESERIALIZABLE_TYPES.add(OffsetDateTime.class); + DESERIALIZABLE_TYPES.add(ZonedDateTime.class); } private static final Logger LOGGER = LoggerFactory.getLogger(ArangoAqlQuery.class); diff --git a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java index 9f8227bed..31669ed6a 100644 --- a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java +++ b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java @@ -1,5 +1,6 @@ package com.arangodb.springframework.repository; +import java.time.Instant; import java.util.Collection; import java.util.List; import java.util.Map; @@ -200,4 +201,7 @@ List findByNestedCustomersNestedCustomerShoppingCartProductsLocationWi @Query("RETURN COUNT(@@collection)") long queryCount(@Param("@collection") Class collection); + + @Query("RETURN DATE_ISO8601(1474988621)") + Instant queryDate(); } diff --git a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java index bbba8c2fb..84cb2b9d0 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.time.Instant; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -181,4 +182,9 @@ public void findManyBySurnameTest() { public void queryCount() { assertEquals(repository.queryCount(Customer.class), 0L); } + + @Test + public void queryDate() { + assertEquals(repository.queryDate(), Instant.ofEpochMilli(1474988621)); + } } From d551b76e203e1e8fa980e027f858aad8836d9d2c Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 20 Apr 2018 13:34:08 +0200 Subject: [PATCH 26/94] Upgrade velocypack-module-joda to 1.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a1de69cd8..f7043852e 100644 --- a/pom.xml +++ b/pom.xml @@ -288,7 +288,7 @@ com.arangodb velocypack-module-joda - 1.1.0 + 1.1.1 From 72bf033ee8e7c44e5d9dd0fb9508b895c86be350 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 20 Apr 2018 14:02:57 +0200 Subject: [PATCH 27/94] Fix simple type conversion --- .../core/convert/ArangoCustomConversions.java | 23 +----- .../core/convert/CustomConversions.java | 3 +- .../core/mapping/ArangoMappingTest.java | 71 ++++++++----------- 3 files changed, 32 insertions(+), 65 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java b/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java index 697a9ca75..6773a4679 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java +++ b/src/main/java/com/arangodb/springframework/core/convert/ArangoCustomConversions.java @@ -20,11 +20,7 @@ package com.arangodb.springframework.core.convert; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; - -import org.springframework.core.convert.converter.Converter; /** * @author Mark Vollmary @@ -33,25 +29,8 @@ */ public class ArangoCustomConversions extends CustomConversions { - private static final Collection STORE_CONVERSIONS; - - static { - final Collection> storeConverters = new ArrayList<>(); - storeConverters.addAll(TimeStringConverters.getConvertersToRegister()); - storeConverters.addAll(JodaTimeStringConverters.getConvertersToRegister()); - storeConverters.addAll(ArangoSimpleTypeConverters.getConvertersToRegister()); - STORE_CONVERSIONS = Collections.unmodifiableCollection(storeConverters); - } - public ArangoCustomConversions(final Collection converters) { - super(merge(converters)); - } - - private static Collection merge(final Collection converters) { - final Collection mergedList = new ArrayList<>(); - mergedList.addAll(STORE_CONVERSIONS); - mergedList.addAll(converters); - return mergedList; + super(converters); } } diff --git a/src/main/java/com/arangodb/springframework/core/convert/CustomConversions.java b/src/main/java/com/arangodb/springframework/core/convert/CustomConversions.java index 4526dc25a..2d842c93a 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/CustomConversions.java +++ b/src/main/java/com/arangodb/springframework/core/convert/CustomConversions.java @@ -41,7 +41,6 @@ import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.data.convert.JodaTimeConverters; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.mapping.model.SimpleTypeHolder; @@ -83,9 +82,9 @@ protected CustomConversions(final Collection converters) { // Add user provided converters to make sure they can override the defaults toRegister.addAll(converters); toRegister.add(CustomToStringConverter.INSTANCE); - toRegister.addAll(JodaTimeConverters.getConvertersToRegister()); toRegister.addAll(TimeStringConverters.getConvertersToRegister()); toRegister.addAll(JodaTimeStringConverters.getConvertersToRegister()); + toRegister.addAll(ArangoSimpleTypeConverters.getConvertersToRegister()); for (final Object c : toRegister) { registerConversion(c); diff --git a/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java b/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java index a111fc7af..6fce9b706 100644 --- a/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java +++ b/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java @@ -28,8 +28,6 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; -import java.math.BigDecimal; -import java.math.BigInteger; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; @@ -38,9 +36,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.UUID; import java.util.stream.Collectors; +import org.joda.time.DateTimeZone; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.annotation.Id; @@ -1020,7 +1018,7 @@ public static class JodaTestEntity extends BasicTestEntity { @Test public void jodaMapping() { final JodaTestEntity entity = new JodaTestEntity(); - entity.value1 = org.joda.time.DateTime.now(); + entity.value1 = org.joda.time.DateTime.now(DateTimeZone.forOffsetHours(1)); entity.value2 = org.joda.time.Instant.now(); entity.value3 = org.joda.time.LocalDate.now(); entity.value4 = org.joda.time.LocalDateTime.now(); @@ -1092,9 +1090,9 @@ public void complexPropertyInheritanceMapping() { .find(entity.getId(), PropertyInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); - ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; + final ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; + final SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; assertThat(simpleDocument.field, is(innerChild.field)); } @@ -1117,7 +1115,7 @@ public void simpleListInheritanceMapping() { final ListInheritanceTestEntity document = template.find(entity.getId(), ListInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(List.class))); - for (BasicTestEntity elem : document.value) { + for (final BasicTestEntity elem : document.value) { assertThat(elem, is(instanceOf(SimpleBasicChildTestEntity.class))); assertThat(((SimpleBasicChildTestEntity) elem).field, is(value)); } @@ -1140,11 +1138,11 @@ public void complexListInheritanceMapping() { final ListInheritanceTestEntity document = template.find(entity.getId(), ListInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(List.class))); - for (BasicTestEntity elem : document.value) { + for (final BasicTestEntity elem : document.value) { assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); - ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; + final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; assertThat(simpleElem.field, is(value)); } } @@ -1172,11 +1170,11 @@ public void untypedListInheritanceMapping() { .find(entity.getId(), UntypedListInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(List.class))); - for (Object elem : document.value) { + for (final Object elem : document.value) { assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); - ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; + final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; assertThat(simpleElem.field, is(value)); } } @@ -1200,7 +1198,7 @@ public void simpleMapInheritanceMapping() { final MapInheritanceTestEntity document = template.find(entity.getId(), MapInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(Map.class))); - for (Map.Entry entry : document.value.entrySet()) { + for (final Map.Entry entry : document.value.entrySet()) { assertThat(entry.getValue(), is(instanceOf(SimpleBasicChildTestEntity.class))); assertThat(((SimpleBasicChildTestEntity) entry.getValue()).field, is(value)); } @@ -1223,11 +1221,11 @@ public void complexMapInheritanceMapping() { final MapInheritanceTestEntity document = template.find(entity.getId(), MapInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(Map.class))); - for (Map.Entry entry : document.value.entrySet()) { + for (final Map.Entry entry : document.value.entrySet()) { assertThat(entry.getValue(), is(instanceOf(ComplexBasicChildTestEntity.class))); - ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) entry.getValue(); + final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) entry.getValue(); assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; assertThat(simpleElem.field, is(value)); } } @@ -1256,12 +1254,12 @@ public void untypedMapInheritanceMapping() { .find(entity.getId(), UntypedMapInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(Map.class))); - for (Object entry : document.value.entrySet()) { + for (final Object entry : document.value.entrySet()) { final Object val = ((Map.Entry) entry).getValue(); assertThat(val, is(instanceOf(ComplexBasicChildTestEntity.class))); - ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) val; + final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) val; assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; assertThat(simpleElem.field, is(value)); } } @@ -1287,9 +1285,9 @@ public void constructorPropertyInheritanceMapping() { .find(entity.getId(), ConstructorWithPropertyInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); - ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; + final ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; + final SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; assertThat(simpleDocument.field, is(innerChild.field)); } @@ -1302,7 +1300,7 @@ public void listInMapInheritanceMapping() { final Map> map = new HashMap<>(); final String value = "value"; for (int i = 0; i < 3; ++i) { - List list = new ArrayList<>(); + final List list = new ArrayList<>(); map.put(String.valueOf(i), list); for (int j = 0; j < 3; ++j) { final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); @@ -1319,13 +1317,13 @@ public void listInMapInheritanceMapping() { .find(entity.getId(), ListInMapInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(Map.class))); - for (Map.Entry> entry : document.value.entrySet()) { + for (final Map.Entry> entry : document.value.entrySet()) { assertThat(entry.getValue(), is(instanceOf(List.class))); - for (BasicTestEntity elem : entry.getValue()) { + for (final BasicTestEntity elem : entry.getValue()) { assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); - ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; + final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; assertThat(simpleElem.field, is(value)); } } @@ -1350,9 +1348,9 @@ public void propertyRefInheritanceMapping() { .find(entity.getId(), PropertyRefInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); - ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; + final ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; + final SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; assertThat(simpleDocument.field, is(innerChild.field)); } @@ -1366,18 +1364,15 @@ public class SimpleTypesTestEntity extends BasicTestEntity { private Double doubleValue; private Character charValue; private Byte byteValue; - private BigInteger bigIntValue; - private BigDecimal bigDecValue; - private UUID uuidValue; private Date dateValue; private java.sql.Date sqlDateValue; private Timestamp timestampValue; private byte[] byteArray; } - + @Test public void simpleTypesMapping() { - SimpleTypesTestEntity entity = new SimpleTypesTestEntity(); + final SimpleTypesTestEntity entity = new SimpleTypesTestEntity(); entity.stringValue = "hello world"; entity.boolValue = true; entity.intValue = 123456; @@ -1387,15 +1382,12 @@ public void simpleTypesMapping() { entity.doubleValue = 1.2345678901234567890; entity.charValue = 'a'; entity.byteValue = 'z'; - entity.bigIntValue = new BigInteger("123456789"); - entity.bigDecValue = new BigDecimal("1.23456789"); - entity.uuidValue = UUID.randomUUID(); entity.dateValue = new Date(); entity.sqlDateValue = new java.sql.Date(new Date().getTime()); entity.timestampValue = new Timestamp(new Date().getTime()); entity.byteArray = new byte[] { 'a', 'b', 'c', 'x', 'y', 'z' }; template.insert(entity); - SimpleTypesTestEntity document = template.find(entity.getId(), SimpleTypesTestEntity.class).get(); + final SimpleTypesTestEntity document = template.find(entity.getId(), SimpleTypesTestEntity.class).get(); assertThat(entity.stringValue, is(document.stringValue)); assertThat(entity.boolValue, is(document.boolValue)); assertThat(entity.intValue, is(document.intValue)); @@ -1405,9 +1397,6 @@ public void simpleTypesMapping() { assertThat(entity.doubleValue, is(document.doubleValue)); assertThat(entity.charValue, is(document.charValue)); assertThat(entity.byteValue, is(document.byteValue)); - assertThat(entity.bigIntValue, is(document.bigIntValue)); - assertThat(entity.bigDecValue, is(document.bigDecValue)); - assertThat(entity.uuidValue, is(document.uuidValue)); assertThat(entity.dateValue, is(document.dateValue)); assertThat(entity.sqlDateValue, is(document.sqlDateValue)); assertThat(entity.timestampValue, is(document.timestampValue)); From 6969ef2b974c0cb488e4cc8a90c51232e0efff70 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 20 Apr 2018 14:03:38 +0200 Subject: [PATCH 28/94] prepare release 1.1.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f7043852e..d3d70365e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.0.3-SNAPSHOT + 1.1.0 2017 jar From 563ad805148fd18e86dd1fcbdb1df69c82d681ab Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 20 Apr 2018 14:07:51 +0200 Subject: [PATCH 29/94] prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d3d70365e..9e6986d17 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.0 + 1.1.1-SNAPSHOT 2017 jar From 4e9ad53c55e87eb69ca9720fe23bb57522891bc7 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 23 Apr 2018 09:32:56 +0200 Subject: [PATCH 30/94] Fix serialization of enums (issue #39) --- ChangeLog | 4 ++++ .../core/mapping/ArangoSimpleTypes.java | 3 ++- .../repository/query/ArangoAqlQuery.java | 3 ++- .../core/mapping/ArangoMappingTest.java | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4c274e9d7..4216010fd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +v1.1.1 (2018-04-23) +--------------------------- +* fixed serialization of enums (issue #39) + v1.1.0 (2018-04-20) --------------------------- * added DataIntegrityViolationException to ExceptionTranslator diff --git a/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java b/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java index 921b191ea..5d811bd2f 100644 --- a/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java +++ b/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java @@ -44,7 +44,7 @@ public abstract class ArangoSimpleTypes { private static final Set> ARANGO_SIMPLE_TYPES; static { - final Set> simpleTypes = new HashSet>(); + final Set> simpleTypes = new HashSet<>(); // com.arangodb.springframework.* simpleTypes.add(DBEntity.class); @@ -70,6 +70,7 @@ public abstract class ArangoSimpleTypes { simpleTypes.add(Double.class); simpleTypes.add(Number.class); simpleTypes.add(String.class); + simpleTypes.add(Enum.class); // arrays simpleTypes.add(byte[].class); diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java index 73bfee61e..ae4fa4924 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java @@ -108,9 +108,10 @@ public class ArangoAqlQuery implements RepositoryQuery { DESERIALIZABLE_TYPES.add(java.sql.Timestamp.class); DESERIALIZABLE_TYPES.add(VPackSlice.class); DESERIALIZABLE_TYPES.add(UUID.class); - DESERIALIZABLE_TYPES.add(new byte[] {}.getClass()); + DESERIALIZABLE_TYPES.add(byte[].class); DESERIALIZABLE_TYPES.add(Byte.class); DESERIALIZABLE_TYPES.add(byte.class); + DESERIALIZABLE_TYPES.add(Enum.class); DESERIALIZABLE_TYPES.add(Instant.class); DESERIALIZABLE_TYPES.add(LocalDate.class); DESERIALIZABLE_TYPES.add(LocalDateTime.class); diff --git a/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java b/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java index 6fce9b706..ce06cab61 100644 --- a/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java +++ b/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java @@ -1403,4 +1403,20 @@ public void simpleTypesMapping() { assertThat(entity.byteArray, is(document.byteArray)); } + public enum TestEnum { + A, B; + } + + public class EnumTestEntity extends BasicTestEntity { + private TestEnum value; + } + + @Test + public void enumMapping() { + final EnumTestEntity entity = new EnumTestEntity(); + entity.value = TestEnum.A; + template.insert(entity); + final EnumTestEntity document = template.find(entity.getId(), EnumTestEntity.class).get(); + assertThat(entity.value, is(document.value)); + } } From 5f61fc80734402788a1f61a4a28a803592db6478 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 23 Apr 2018 09:39:42 +0200 Subject: [PATCH 31/94] prepare release 1.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9e6986d17..5e935a4f4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.1-SNAPSHOT + 1.1.1 2017 jar From fe2d917deb6218fc48e4a8f79a48eafd709f1345 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 23 Apr 2018 09:43:35 +0200 Subject: [PATCH 32/94] prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5e935a4f4..fbedb0768 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.1 + 1.1.2-SNAPSHOT 2017 jar From 75e4b59ec60d90a601905fd24e20922dd190376a Mon Sep 17 00:00:00 2001 From: Christian Lechner <6638938+christian-lechner@users.noreply.github.com> Date: Thu, 3 May 2018 10:40:13 +0200 Subject: [PATCH 33/94] Repository query execution refactored (ArangoAqlQuery) (#42) * Param annotation deprecated * add persistent entity info * query lookup strategy moved * refactoring of repository query impl * fix authors * remove unnecessary assertion * rename * fix imports * rename * update imports * update tests * remove lazy * update authors * update authors take two * fix locale dependent bug when using String.format("%f", 1.2) * also add full count to query options if method is string page query * make error messages more user friendly * make version volatile * add error message when full result count is missing in query result * add more simple types * isEntityType should make use of the actual simple types * move deserializable types to cursor iterator * fix typo * let ArangoConverter extends EntityConverter * use the spring data mapping context interface on properties * update isGeoQuery() * add final modifier * set AqlQueryOptions#fullCount(true) also on GeoPage query * change named queries file name * update doc * fix spelling / add more info --- docs/Drivers/SpringData/Reference/README.md | 211 +++++---- .../annotation/EnableArangoRepositories.java | 22 +- .../springframework/annotation/Param.java | 8 +- .../core/convert/ArangoConverter.java | 11 +- .../core/convert/DefaultArangoConverter.java | 12 +- .../core/mapping/ArangoSimpleTypes.java | 25 +- .../template/ArangoExtCursorIterator.java | 34 +- .../core/template/ArangoTemplate.java | 2 +- .../repository/ArangoEntityInformation.java | 35 ++ .../ArangoPersistentEntityInformation.java | 48 ++ ...rangoRepositoryConfigurationExtension.java | 33 +- .../repository/ArangoRepositoryFactory.java | 68 ++- .../repository/query/AbstractArangoQuery.java | 173 ++++++++ .../repository/query/ArangoAqlQuery.java | 412 ------------------ .../query/ArangoParameterAccessor.java | 71 +-- .../repository/query/ArangoParameters.java | 172 +++++++- .../ArangoParametersParameterAccessor.java | 63 +++ .../query/ArangoQueryLookupStrategy.java | 52 --- .../repository/query/ArangoQueryMethod.java | 97 ++++- .../query/ArangoResultConverter.java | 13 +- .../repository/query/DerivedArangoQuery.java | 90 ++++ .../query/StringBasedArangoQuery.java | 141 ++++++ .../query/derived/DerivedQueryCreator.java | 267 ++++++------ .../ArangoTestConfiguration.java | 7 +- .../repository/CustomerRepository.java | 62 ++- .../repository/query/ArangoAqlQueryTest.java | 84 ++-- .../derived/DerivedQueryCreatorTest.java | 5 +- .../arango-named-queries-test.properties | 1 + 28 files changed, 1328 insertions(+), 891 deletions(-) create mode 100644 src/main/java/com/arangodb/springframework/repository/ArangoEntityInformation.java create mode 100644 src/main/java/com/arangodb/springframework/repository/ArangoPersistentEntityInformation.java create mode 100644 src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java delete mode 100644 src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java create mode 100644 src/main/java/com/arangodb/springframework/repository/query/ArangoParametersParameterAccessor.java delete mode 100644 src/main/java/com/arangodb/springframework/repository/query/ArangoQueryLookupStrategy.java create mode 100644 src/main/java/com/arangodb/springframework/repository/query/DerivedArangoQuery.java create mode 100644 src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java create mode 100644 src/test/resources/arango-named-queries-test.properties diff --git a/docs/Drivers/SpringData/Reference/README.md b/docs/Drivers/SpringData/Reference/README.md index 1b729a561..675cbb97c 100644 --- a/docs/Drivers/SpringData/Reference/README.md +++ b/docs/Drivers/SpringData/Reference/README.md @@ -57,7 +57,7 @@ public interface MyRepository extends Repository{ } ``` - In addition you can use a parameter of type `Map` annotated with `@BindVars` as your bind parameters. You can then fill the map with any parameter used in the query. (see [here](https://docs.arangodb.com/3.1/AQL/Fundamentals/BindParameters.html#bind-parameters) for more Information about Bind Parameters). +In addition you can use a parameter of type `Map` annotated with `@BindVars` as your bind parameters. You can then fill the map with any parameter used in the query. (see [here](https://docs.arangodb.com/3.1/AQL/Fundamentals/BindParameters.html#bind-parameters) for more Information about Bind Parameters). ```java public interface MyRepository extends Repository{ @@ -79,37 +79,56 @@ public interface MyRepository extends Repository{ } ``` +## Named queries + +An alternative to using the `@Query` annotation on methods is specifying them in a separate `.properties` file. The default path for the file is `META-INF/arango-named-queries.properties` and can be changed with the `EnableArangoRepositories#namedQueriesLocation()` setting. The entries in the properties file must adhere to the following convention: `{simple entity name}.{method name} = {query}`. Let's assume we have the following repository interface: + +```java +package com.arangodb.repository; + +public interface CustomerRepository extends ArangoRepository { + Customer findByUsername(@Param("username") String username); +} +``` + +The corresponding `arango-named-queries.properties` file looks like this: + +```properties +Customer.findByUsername = FOR c IN customers FILTER c.username == @username RETURN c +``` + +The queries specified in the properties file are no different than the queries that can be defined with the `@Query` annotation. The only difference is that the queries are in one place. If there is a `@Query` annotation present and a named query defined, the query in the `@Query` annotation takes precedence. + ## Derived queries Spring Data ArangoDB supports queries derived from methods names by splitting it into its semantic parts and converting into AQL. The mechanism strips the prefixes `find..By`, `get..By`, `query..By`, `read..By`, `stream..By`, `count..By`, `exists..By`, `delete..By`, `remove..By` from the method and parses the rest. The By acts as a separator to indicate the start of the criteria for the query to be built. You can define conditions on entity properties and concatenate them with `And` and `Or`. The complete list of part types for derived methods is below, where doc is a document in the database -Keyword | Sample | Predicate -----------|----------------|-------- -IsGreaterThan, GreaterThan, After | findByAgeGreaterThan(int age) | doc.age > age -IsGreaterThanEqual, GreaterThanEqual | findByAgeIsGreaterThanEqual(int age) | doc.age >= age -IsLessThan, LessThan, Before | findByAgeIsLessThan(int age) | doc.age < age -IsLessThanEqualLessThanEqual | findByAgeLessThanEqual(int age) | doc.age <= age -IsBetween, Between | findByAgeBetween(int lower, int upper) | lower < doc.age < upper -IsNotNull, NotNull | findByNameNotNull() | doc.name != null -IsNull, Null | findByNameNull() | doc.name == null -IsLike, Like | findByNameLike(String name) | doc.name LIKE name -IsNotLike, NotLike | findByNameNotLike(String name) | NOT(doc.name LIKE name) -IsStartingWith, StartingWith, StartsWith | findByNameStartsWith(String prefix) | doc.name LIKE prefix -IsEndingWith, EndingWith, EndsWith | findByNameEndingWith(String suffix) | doc.name LIKE suffix -Regex, MatchesRegex, Matches | findByNameRegex(String pattern) | REGEX_TEST(doc.name, name, ignoreCase) -(No Keyword) | findByFirstName(String name) | doc.name == name -IsTrue, True | findByActiveTrue() | doc.active == true -IsFalse, False | findByActiveFalse() | doc.active == false -Is, Equals | findByAgeEquals(int age) | doc.age == age -IsNot, Not | findByAgeNot(int age) | doc.age != age -IsIn, In | findByNameIn(String[] names) | doc.name IN names -IsNotIn, NotIn | findByNameIsNotIn(String[] names) | doc.name NOT IN names -IsContaining, Containing, Contains | findByFriendsContaining(String name) | name IN doc.friends -IsNotContaining, NotContaining, NotContains | findByFriendsNotContains(String name) | name NOT IN doc.friends -Exists | findByFriendNameExists() | HAS(doc.friend, name) - +| Keyword | Sample | Predicate | +| ------------------------------------------- | -------------------------------------- | -------------------------------------- | +| IsGreaterThan, GreaterThan, After | findByAgeGreaterThan(int age) | doc.age > age | +| IsGreaterThanEqual, GreaterThanEqual | findByAgeIsGreaterThanEqual(int age) | doc.age >= age | +| IsLessThan, LessThan, Before | findByAgeIsLessThan(int age) | doc.age < age | +| IsLessThanEqualLessThanEqual | findByAgeLessThanEqual(int age) | doc.age <= age | +| IsBetween, Between | findByAgeBetween(int lower, int upper) | lower < doc.age < upper | +| IsNotNull, NotNull | findByNameNotNull() | doc.name != null | +| IsNull, Null | findByNameNull() | doc.name == null | +| IsLike, Like | findByNameLike(String name) | doc.name LIKE name | +| IsNotLike, NotLike | findByNameNotLike(String name) | NOT(doc.name LIKE name) | +| IsStartingWith, StartingWith, StartsWith | findByNameStartsWith(String prefix) | doc.name LIKE prefix | +| IsEndingWith, EndingWith, EndsWith | findByNameEndingWith(String suffix) | doc.name LIKE suffix | +| Regex, MatchesRegex, Matches | findByNameRegex(String pattern) | REGEX_TEST(doc.name, name, ignoreCase) | +| (No Keyword) | findByFirstName(String name) | doc.name == name | +| IsTrue, True | findByActiveTrue() | doc.active == true | +| IsFalse, False | findByActiveFalse() | doc.active == false | +| Is, Equals | findByAgeEquals(int age) | doc.age == age | +| IsNot, Not | findByAgeNot(int age) | doc.age != age | +| IsIn, In | findByNameIn(String[] names) | doc.name IN names | +| IsNotIn, NotIn | findByNameIsNotIn(String[] names) | doc.name NOT IN names | +| IsContaining, Containing, Contains | findByFriendsContaining(String name) | name IN doc.friends | +| IsNotContaining, NotContaining, NotContains | findByFriendsNotContains(String name) | name NOT IN doc.friends | +| Exists | findByFriendNameExists() | HAS(doc.friend, name) | ```java public interface MyRepository extends Repository { @@ -136,7 +155,7 @@ You can apply sorting for one or multiple sort criteria by appending `OrderBy` t public interface MyRepository extends Repository { // FOR c IN customers - // FITLER c.name == @0 + // FILTER c.name == @0 // SORT c.age DESC RETURN c ArangoCursor getByNameOrderByAgeDesc(String name); @@ -154,7 +173,7 @@ Geospatial queries are a subsection of derived queries. To use a geospatial quer As a subsection of derived queries, geospatial queries support all the same return types, but also support the three return types `GeoPage, GeoResult and Georesults`. These types must be used in order to get the distance of each document as generated by the query. -There are two kinds of geospatial query, Near and Within. Near sorts documents by distance from the given point, while within both sorts and filters documents, returning those within the given distance range or shape. +There are two kinds of geospatial query, Near and Within. Near sorts documents by distance from the given point, while within both sorts and filters documents, returning those within the given distance range or shape. ```java public interface MyRepository extends Repository { @@ -194,7 +213,7 @@ public interface MyRepository extends Repository { } ``` -It is possible for the algorithm to select the wrong property if the domain class also has a property which matches the first split of the expression. To resolve this ambiguity you can use _ as a separator inside your method-name to define traversal points. +It is possible for the algorithm to select the wrong property if the domain class also has a property which matches the first split of the expression. To resolve this ambiguity you can use \_ as a separator inside your method-name to define traversal points. ```java @Document("customers") @@ -231,7 +250,7 @@ public interface MyRepository extends Repository { ### Bind parameters -AQL supports the usage of [bind parameters](https://docs.arangodb.com/3.1/AQL/Fundamentals/BindParameters.html) which you can define with a method parameter named `bindVars` of type `Map`. +AQL supports the usage of [bind parameters](https://docs.arangodb.com/3.1/AQL/Fundamentals/BindParameters.html) which you can define with a method parameter annotated with `@BindVars` of type `Map`. ```java public interface MyRepository extends Repository { @@ -301,26 +320,27 @@ In this section we will describe the features and conventions for mapping Java o ArangoDB uses [VelocyPack](https://github.com/arangodb/velocypack) as it's internal storage format which supports a large number of data types. In addition Spring Data ArangoDB offers - with the underlying Java driver - built-in converters to add additional types to the mapping. -Java type | VelocyPack type -----------|---------------- -java.lang.String | string -java.lang.Boolean | bool -java.lang.Integer | signed int 4 bytes, smallint -java.lang.Long | signed int 8 bytes, smallint -java.lang.Short | signed int 2 bytes, smallint -java.lang.Double | double -java.lang.Float | double -java.math.BigInteger | signed int 8 bytes, unsigned int 8 bytes -java.math.BigDecimal | double -java.lang.Number | double -java.lang.Character | string -java.util.Date | string (date-format ISO 8601) -java.sql.Date | string (date-format ISO 8601) -java.sql.Timestamp | string (date-format ISO 8601) -java.util.UUID | string -java.lang.byte[] | string (Base64) +| Java type | VelocyPack type | +| -------------------- | ---------------------------------------- | +| java.lang.String | string | +| java.lang.Boolean | bool | +| java.lang.Integer | signed int 4 bytes, smallint | +| java.lang.Long | signed int 8 bytes, smallint | +| java.lang.Short | signed int 2 bytes, smallint | +| java.lang.Double | double | +| java.lang.Float | double | +| java.math.BigInteger | signed int 8 bytes, unsigned int 8 bytes | +| java.math.BigDecimal | double | +| java.lang.Number | double | +| java.lang.Character | string | +| java.util.Date | string (date-format ISO 8601) | +| java.sql.Date | string (date-format ISO 8601) | +| java.sql.Timestamp | string (date-format ISO 8601) | +| java.util.UUID | string | +| java.lang.byte[] | string (Base64) | ## Type mapping + As collections in ArangoDB can contain documents of various types, a mechanism to retrieve the correct Java class is required. The type information of properties declared in a class may not be enough to restore the original class (due to inheritance). If the declared complex type and the actual type do not match, information about the actual type is stored together with the document. This is necessary to restore the correct type when reading from the DB. Consider the following example: ```java @@ -328,7 +348,7 @@ public class Person { private String name; private Address homeAddress; // ... - + // getters and setters omitted } @@ -354,7 +374,7 @@ public class Address { @Document public class Company { - @Key + @Key private String key; private Person manager; @@ -376,12 +396,12 @@ The serialized document for the DB looks like this: "manager": { "name": "Jane Roberts", "homeAddress": { - "street": "Park Avenue", - "number": "432/64" + "street": "Park Avenue", + "number": "432/64" }, "workAddress": { - "street": "Main Street", - "number": "223" + "street": "Main Street", + "number": "223" }, "_class": "com.arangodb.Employee" }, @@ -392,6 +412,7 @@ The serialized document for the DB looks like this: Type hints are written for top-level documents (as a collection can contain different document types) as well as for every value if it's a complex type and a sub-type of the property type declared. `Map`s and `Collection`s are excluded from type mapping. Without the additional information about the concrete classes used, the document couldn't be restored in Java. The type information of the `manager` property is not enough to determine the `Employee` type. The `homeAddress` and `workAddress` properties have the same actual and defined type, thus no type hint is needed. ### Customizing type mapping + By default, the fully qualified class name is stored in the documents as a type hint. A custom type hint can be set with the `@TypeAlias("my-alias")` annotation on an entity. Make sure that it is an unique identifier across all entities. If we would add a `TypeAlias("employee")` annotation to the `Employee` class above, it would be persisted as `"_class": "employee"`. The default type key is `_class` and can be changed by overriding the `typeKey()` method of the `AbstractArangoConfiguration` class. @@ -401,37 +422,38 @@ If you need to further customize the type mapping process, the `arangoTypeMapper In order to fully customize the type mapping process you can provide a custom type mapper implementation by extending the `DefaultArangoTypeMapper` class. ### Deactivating type mapping + To deactivate the type mapping process, you can return `null` from the `typeKey()` method of the `AbstractArangoConfiguration` class. No type hints are stored in the documents with this setting. If you make sure that each defined type corresponds to the actual type, you can disable the type mapping, otherwise it can lead to exceptions when reading the entities from the DB. ## Annotations ### Annotation overview -annotation | level | description ------------|-------|------------ -@Document | class | marks this class as a candidate for mapping -@Edge | class | marks this class as a candidate for mapping -@Id | field | stores the field as the system field _id -@Key | field | stores the field as the system field _key -@Rev | field | stores the field as the system field _rev -@Field("alt-name") | field | stores the field with an alternative name -@Ref | field | stores the _id of the referenced document and not the nested document -@From | field | stores the _id of the referenced document as the system field _from -@To | field | stores the _id of the referenced document as the system field _to -@Relations | field | vertices which are connected over edges -@Transient | field, method, annotation | marks a field to be transient for the mapping framework, thus the property will not be persisted and not further inspected by the mapping framework -@PersistenceConstructor | constructor | marks a given constructor - even a package protected one - to use when instantiating the object from the database -@TypeAlias("alias") | class | set a type alias for the class when persisted to the DB -@HashIndex | class | describes a hash index -@HashIndexed | field | describes how to index the field -@SkiplistIndex | class | describes a skiplist index -@SkiplistIndexed | field | describes how to index the field -@PersistentIndex | class | describes a persistent index -@PersistentIndexed | field | describes how to index the field -@GeoIndex | class | describes a geo index -@GeoIndexed | field | describes how to index the field -@FulltextIndex | class | describes a fulltext index -@FulltextIndexed | field | describes how to index the field +| annotation | level | description | +| ----------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| @Document | class | marks this class as a candidate for mapping | +| @Edge | class | marks this class as a candidate for mapping | +| @Id | field | stores the field as the system field \_id | +| @Key | field | stores the field as the system field \_key | +| @Rev | field | stores the field as the system field \_rev | +| @Field("alt-name") | field | stores the field with an alternative name | +| @Ref | field | stores the \_id of the referenced document and not the nested document | +| @From | field | stores the \_id of the referenced document as the system field \_from | +| @To | field | stores the \_id of the referenced document as the system field \_to | +| @Relations | field | vertices which are connected over edges | +| @Transient | field, method, annotation | marks a field to be transient for the mapping framework, thus the property will not be persisted and not further inspected by the mapping framework | +| @PersistenceConstructor | constructor | marks a given constructor - even a package protected one - to use when instantiating the object from the database | +| @TypeAlias("alias") | class | set a type alias for the class when persisted to the DB | +| @HashIndex | class | describes a hash index | +| @HashIndexed | field | describes how to index the field | +| @SkiplistIndex | class | describes a skiplist index | +| @SkiplistIndexed | field | describes how to index the field | +| @PersistentIndex | class | describes a persistent index | +| @PersistentIndexed | field | describes how to index the field | +| @GeoIndex | class | describes a geo index | +| @GeoIndexed | field | describes how to index the field | +| @FulltextIndex | class | describes a fulltext index | +| @FulltextIndexed | field | describes how to index the field | ### Document @@ -475,7 +497,7 @@ public class Address { } ``` -The database representation of `Person` in collection *persons* looks as follow: +The database representation of `Person` in collection _persons_ looks as follow: ``` { @@ -484,7 +506,9 @@ The database representation of `Person` in collection *persons* looks as follow: "address" : "addresses/456" } ``` -and the representation of `Address` in collection *addresses*: + +and the representation of `Address` in collection _addresses_: + ``` { "_key" : "456", @@ -526,7 +550,7 @@ public class Relation { ### Document with From and To -With the annotations `@From` and `@To` applied on a collection or array field in a class annotated with `@Document` the nested edge objects are fetched from the database. Each of the nested edge objects has to be stored as separate edge document in the edge collection described in the `@Edge` annotation of the nested object class with the *_id* of the parent document as field *_from* or *_to*. +With the annotations `@From` and `@To` applied on a collection or array field in a class annotated with `@Document` the nested edge objects are fetched from the database. Each of the nested edge objects has to be stored as separate edge document in the edge collection described in the `@Edge` annotation of the nested object class with the _\_id_ of the parent document as field _\_from_ or _\_to_. ```java @Document("persons") @@ -541,7 +565,8 @@ public class Relation { } ``` -The database representation of `Person` in collection *persons* looks as follow: +The database representation of `Person` in collection _persons_ looks as follow: + ``` { "_key" : "123", @@ -549,7 +574,8 @@ The database representation of `Person` in collection *persons* looks as follow: } ``` -and the representation of `Relation` in collection *relations*: +and the representation of `Relation` in collection _relations_: + ``` { "_key" : "456", @@ -564,12 +590,11 @@ and the representation of `Relation` in collection *relations*: "_to" : ".../..." } ... - ``` ### Edge with From and To -With the annotations `@From` and `@To` applied on a field in a class annotated with `@Edge` the nested object is fetched from the database. The nested object has to be stored as a separate document in the collection described in the `@Document` annotation of the nested object class. The *_id* field of this nested object is stored in the fields `_from` or `_to` within the edge document. +With the annotations `@From` and `@To` applied on a field in a class annotated with `@Edge` the nested object is fetched from the database. The nested object has to be stored as a separate document in the collection described in the `@Document` annotation of the nested object class. The _\_id_ field of this nested object is stored in the fields `_from` or `_to` within the edge document. ```java @Edge("relations") @@ -587,7 +612,8 @@ public class Person { } ``` -The database representation of `Relation` in collection *relations* looks as follow: +The database representation of `Relation` in collection _relations_ looks as follow: + ``` { "_key" : "123", @@ -597,7 +623,8 @@ The database representation of `Relation` in collection *relations* looks as fol } ``` -and the representation of `Person` in collection *persons*: +and the representation of `Person` in collection _persons_: + ``` { "_key" : "456", @@ -616,6 +643,7 @@ and the representation of `Person` in collection *persons*: With the `@Indexed` annotations user defined indexes can be created at a collection level by annotating single fields of a class. Possible `@Indexed` annotations are: + * `@HashIndexed` * `@SkiplistIndexed` * `@PersistentIndexed` @@ -623,6 +651,7 @@ Possible `@Indexed` annotations are: * `@FulltextIndexed` The following example creates a hash index on the field `name` and a separate hash index on the field `age`: + ```java public class Person { @HashIndexed @@ -636,6 +665,7 @@ public class Person { With the `@Indexed` annotations different indexes can be created on the same field. The following example creates a hash index and also a skiplist index on the field `name`: + ```java public class Person { @HashIndexed @@ -647,6 +677,7 @@ public class Person { If the index should include multiple fields the `@Index` annotations can be used on the type instead. Possible `@Index` annotations are: + * `@HashIndex` * `@SkiplistIndex` * `@PersistentIndex` @@ -654,6 +685,7 @@ Possible `@Index` annotations are: * `@FulltextIndex` The following example creates a single hash index on the fields `name` and `age`, note that if a field is renamed in the database with @Field, the new field name must be used in the index declaration: + ```java @HashIndex(fields = {"fullname", "age"}) public class Person { @@ -667,6 +699,7 @@ public class Person { The `@Index` annotations can also be used to create an index on a nested field. The following example creates a single hash index on the fields `name` and `address.country`: + ```java @HashIndex(fields = {"name", "address.country"}) public class Person { @@ -679,6 +712,7 @@ public class Person { The `@Index` annotations and the `@Indexed` annotations can be used at the same time in one class. The following example creates a hash index on the fields `name` and `age` and a separate hash index on the field `age`: + ```java @HashIndex(fields = {"name", "age"}) public class Person { @@ -692,6 +726,7 @@ public class Person { The `@Index` annotations can be used multiple times to create more than one index in this way. The following example creates a hash index on the fields `name` and `age` and a separate hash index on the fields `name` and `gender`: + ```java @HashIndex(fields = {"name", "age"}) @HashIndex(fields = {"name", "gender"}) diff --git a/src/main/java/com/arangodb/springframework/annotation/EnableArangoRepositories.java b/src/main/java/com/arangodb/springframework/annotation/EnableArangoRepositories.java index 12fa3d74a..6f917f63e 100644 --- a/src/main/java/com/arangodb/springframework/annotation/EnableArangoRepositories.java +++ b/src/main/java/com/arangodb/springframework/annotation/EnableArangoRepositories.java @@ -29,12 +29,17 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; +import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.data.repository.query.QueryLookupStrategy.Key; import com.arangodb.springframework.repository.ArangoRepositoriesRegistrar; import com.arangodb.springframework.repository.ArangoRepositoryFactoryBean; /** - * Created by F625633 on 07/07/2017. + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -53,10 +58,23 @@ ComponentScan.Filter[] excludeFilters() default {}; - String repositoryImplementationPostfix() default ""; + /** + * Returns the postfix to be used for custom repository implementations. Defaults to {@literal Impl}. + */ + String repositoryImplementationPostfix() default "Impl"; Class repositoryFactoryBeanClass() default ArangoRepositoryFactoryBean.class; + /** + * Configures the location of the Spring Data named queries properties file. Defaults to + * {@code META-INF/arango-named-queries.properties}. + */ String namedQueriesLocation() default ""; + /** + * Returns the key of the {@link QueryLookupStrategy} that should be used to lookup queries for query methods. + * Currently only the default {@link Key#CREATE_IF_NOT_FOUND} is supported. + */ + Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND; + } diff --git a/src/main/java/com/arangodb/springframework/annotation/Param.java b/src/main/java/com/arangodb/springframework/annotation/Param.java index f7c036aa0..0384b78b6 100644 --- a/src/main/java/com/arangodb/springframework/annotation/Param.java +++ b/src/main/java/com/arangodb/springframework/annotation/Param.java @@ -26,8 +26,14 @@ import java.lang.annotation.Target; /** - * Created by F625633 on 13/07/2017. + * Deprecated. Please use {@link org.springframework.data.repository.query.Param} instead. + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner */ +@Deprecated @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface Param { diff --git a/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java b/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java index ab2d0ab87..9ed20e331 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java @@ -20,8 +20,7 @@ package com.arangodb.springframework.core.convert; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.convert.EntityConverter; import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; @@ -31,16 +30,14 @@ * @author Christian Lechner * */ -public interface ArangoConverter extends ArangoEntityReader, ArangoEntityWriter { - - MappingContext, ArangoPersistentProperty> getMappingContext(); +public interface ArangoConverter + extends EntityConverter, ArangoPersistentProperty, Object, DBEntity>, + ArangoEntityReader, ArangoEntityWriter { boolean isCollectionType(Class type); boolean isEntityType(Class type); - GenericConversionService getConversionService(); - ArangoTypeMapper getTypeMapper(); } diff --git a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java index 3015139db..aea39a400 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java @@ -49,12 +49,10 @@ import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; -import com.arangodb.entity.BaseDocument; -import com.arangodb.entity.BaseEdgeDocument; import com.arangodb.springframework.core.convert.resolver.ResolverFactory; import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; -import com.arangodb.velocypack.VPackSlice; +import com.arangodb.springframework.core.mapping.ArangoSimpleTypes; /** * @author Mark Vollmary @@ -491,8 +489,8 @@ private DBEntity createDBEntity(final Class type) { return isCollectionType(type) ? new DBCollectionEntity() : new DBDocumentEntity(); } - private boolean isSimpleType(final Class type) { - return conversions.isSimpleType(type); + private boolean isArangoSimpleType(final Class type) { + return ArangoSimpleTypes.HOLDER.isSimpleType(type); } @Override @@ -511,9 +509,7 @@ public GenericConversionService getConversionService() { @Override public boolean isEntityType(final Class type) { - return !isSimpleType(type) && !isMapType(type) && !isCollectionType(type) - && !BaseDocument.class.isAssignableFrom(type) && !BaseEdgeDocument.class.isAssignableFrom(type) - && !VPackSlice.class.isAssignableFrom(type); + return !isArangoSimpleType(type) && !isMapType(type) && !isCollectionType(type); } @SuppressWarnings("unchecked") diff --git a/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java b/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java index 5d811bd2f..4d9d649b8 100644 --- a/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java +++ b/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java @@ -31,7 +31,10 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; +import com.arangodb.entity.BaseDocument; +import com.arangodb.entity.BaseEdgeDocument; import com.arangodb.springframework.core.convert.DBEntity; +import com.arangodb.velocypack.VPackSlice; /** * This class contains all types that are directly supported by the Java driver (through java-velocypack). @@ -46,23 +49,36 @@ public abstract class ArangoSimpleTypes { static { final Set> simpleTypes = new HashSet<>(); - // com.arangodb.springframework.* + // com.arangodb.* simpleTypes.add(DBEntity.class); + simpleTypes.add(BaseDocument.class); + simpleTypes.add(BaseEdgeDocument.class); + simpleTypes.add(VPackSlice.class); // primitives + simpleTypes.add(boolean.class); simpleTypes.add(byte.class); simpleTypes.add(char.class); - simpleTypes.add(boolean.class); simpleTypes.add(short.class); simpleTypes.add(int.class); simpleTypes.add(long.class); simpleTypes.add(float.class); simpleTypes.add(double.class); + + // primitive arrays + simpleTypes.add(boolean[].class); + simpleTypes.add(byte[].class); + simpleTypes.add(char[].class); + simpleTypes.add(short[].class); + simpleTypes.add(int[].class); + simpleTypes.add(long[].class); + simpleTypes.add(float[].class); + simpleTypes.add(double[].class); // java.lang.* + simpleTypes.add(Boolean.class); simpleTypes.add(Byte.class); simpleTypes.add(Character.class); - simpleTypes.add(Boolean.class); simpleTypes.add(Short.class); simpleTypes.add(Integer.class); simpleTypes.add(Long.class); @@ -72,9 +88,6 @@ public abstract class ArangoSimpleTypes { simpleTypes.add(String.class); simpleTypes.add(Enum.class); - // arrays - simpleTypes.add(byte[].class); - // java.math.* simpleTypes.add(BigInteger.class); simpleTypes.add(BigDecimal.class); diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java index 4f6ae08d3..5de2ee93b 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java @@ -20,6 +20,16 @@ package com.arangodb.springframework.core.template; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.data.mapping.model.SimpleTypeHolder; + import com.arangodb.ArangoCursor; import com.arangodb.entity.CursorEntity; import com.arangodb.internal.ArangoCursorExecute; @@ -31,11 +41,29 @@ /** * @author Mark Vollmary + * @author Christian Lechner + * * @param * */ class ArangoExtCursorIterator extends ArangoCursorIterator { + private static final SimpleTypeHolder ADDITIONAL_DESERIALIZABLE_TYPES; + + static { + final Set> simpleTypes = new HashSet<>(); + + // the following types apply only if the VPackJdk8Module is present on the ArangoDB Java driver, + // but there is no possibility to check + simpleTypes.add(Instant.class); + simpleTypes.add(LocalDate.class); + simpleTypes.add(LocalDateTime.class); + simpleTypes.add(OffsetDateTime.class); + simpleTypes.add(ZonedDateTime.class); + + ADDITIONAL_DESERIALIZABLE_TYPES = new SimpleTypeHolder(simpleTypes, false); + } + private ArangoConverter converter; protected ArangoExtCursorIterator(final ArangoCursor cursor, final InternalArangoDatabase db, @@ -49,8 +77,12 @@ public void setConverter(final ArangoConverter converter) { @Override protected R deserialize(final VPackSlice result, final Class type) { - return !converter.isEntityType(type) ? super.deserialize(result, type) + return canDeserializeDirectly(type) ? super.deserialize(result, type) : converter.read(type, super.deserialize(result, DBEntity.class)); } + private boolean canDeserializeDirectly(final Class type) { + return !converter.isEntityType(type) || ADDITIONAL_DESERIALIZABLE_TYPES.isSimpleType(type); + } + } 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 8c15a81e4..19bdb0d9e 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -82,7 +82,7 @@ */ public class ArangoTemplate implements ArangoOperations, CollectionCallback { - private ArangoDBVersion version; + private volatile ArangoDBVersion version; private final PersistenceExceptionTranslator exceptionTranslator; private final ArangoConverter converter; private final ArangoDB arango; diff --git a/src/main/java/com/arangodb/springframework/repository/ArangoEntityInformation.java b/src/main/java/com/arangodb/springframework/repository/ArangoEntityInformation.java new file mode 100644 index 000000000..3e07489d9 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/ArangoEntityInformation.java @@ -0,0 +1,35 @@ +/* + * 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.io.Serializable; + +import org.springframework.data.repository.core.EntityInformation; + +/** + * + * @author Christian Lechner + */ +public interface ArangoEntityInformation extends EntityInformation { + + String getCollection(); + +} \ No newline at end of file diff --git a/src/main/java/com/arangodb/springframework/repository/ArangoPersistentEntityInformation.java b/src/main/java/com/arangodb/springframework/repository/ArangoPersistentEntityInformation.java new file mode 100644 index 000000000..ebe96d1c1 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/ArangoPersistentEntityInformation.java @@ -0,0 +1,48 @@ +/* + * 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 org.springframework.data.repository.core.support.PersistentEntityInformation; + +import java.io.Serializable; + +import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; + +/** + * + * @author Christian Lechner + */ +public class ArangoPersistentEntityInformation extends PersistentEntityInformation + implements ArangoEntityInformation { + + private final ArangoPersistentEntity persistentEntity; + + public ArangoPersistentEntityInformation(final ArangoPersistentEntity entity) { + super(entity); + this.persistentEntity = entity; + } + + @Override + public String getCollection() { + return persistentEntity.getCollection(); + } + +} diff --git a/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryConfigurationExtension.java b/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryConfigurationExtension.java index 7cc9b59d8..88d28c00e 100644 --- a/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryConfigurationExtension.java +++ b/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryConfigurationExtension.java @@ -20,19 +20,48 @@ package com.arangodb.springframework.repository; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; +import com.arangodb.springframework.annotation.Document; +import com.arangodb.springframework.annotation.Edge; + /** - * Created by F625633 on 07/07/2017. + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner */ public class ArangoRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport { + + @Override + public String getModuleName() { + return "ArangoDB"; + } + @Override protected String getModulePrefix() { - return null; + return "arango"; } @Override public String getRepositoryFactoryClassName() { return ArangoRepositoryFactoryBean.class.getName(); } + + @Override + protected Collection> getIdentifyingAnnotations() { + return Arrays.asList(Document.class, Edge.class); + } + + @Override + protected Collection> getIdentifyingTypes() { + return Collections.singleton(ArangoRepository.class); + } + } diff --git a/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java b/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java index d22673ba3..df259e411 100644 --- a/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java +++ b/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java @@ -21,31 +21,48 @@ package com.arangodb.springframework.repository; import java.io.Serializable; +import java.lang.reflect.Method; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.EntityInformation; +import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.QueryLookupStrategy; +import org.springframework.data.repository.query.RepositoryQuery; import com.arangodb.springframework.core.ArangoOperations; -import com.arangodb.springframework.repository.query.ArangoQueryLookupStrategy; +import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; +import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; +import com.arangodb.springframework.repository.query.ArangoQueryMethod; +import com.arangodb.springframework.repository.query.DerivedArangoQuery; +import com.arangodb.springframework.repository.query.StringBasedArangoQuery; /** - * Created by F625633 on 06/07/2017. + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner */ public class ArangoRepositoryFactory extends RepositoryFactorySupport { private final ArangoOperations arangoOperations; + private final MappingContext, ArangoPersistentProperty> context; public ArangoRepositoryFactory(final ArangoOperations arangoOperations) { this.arangoOperations = arangoOperations; + this.context = arangoOperations.getConverter().getMappingContext(); } + @SuppressWarnings("unchecked") @Override public EntityInformation getEntityInformation(final Class domainClass) { - return null; + return new ArangoPersistentEntityInformation( + (ArangoPersistentEntity) context.getPersistentEntity(domainClass)); } @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -60,10 +77,47 @@ protected Class getRepositoryBaseClass(final RepositoryMetadata metadata) { } @Override - protected QueryLookupStrategy getQueryLookupStrategy( - final QueryLookupStrategy.Key key, - final EvaluationContextProvider evaluationContextProvider) { - return new ArangoQueryLookupStrategy(arangoOperations); + protected QueryLookupStrategy getQueryLookupStrategy(final QueryLookupStrategy.Key key, + final EvaluationContextProvider evaluationContextProvider) { + + QueryLookupStrategy strategy = null; + switch (key) { + case CREATE_IF_NOT_FOUND: + strategy = new DefaultArangoQueryLookupStrategy(arangoOperations); + break; + case CREATE: + break; + case USE_DECLARED_QUERY: + break; + } + return strategy; + } + + static class DefaultArangoQueryLookupStrategy implements QueryLookupStrategy { + + private final ArangoOperations operations; + + public DefaultArangoQueryLookupStrategy(final ArangoOperations operations) { + this.operations = operations; + } + + @Override + public RepositoryQuery resolveQuery(final Method method, final RepositoryMetadata metadata, + final ProjectionFactory factory, final NamedQueries namedQueries) { + + final ArangoQueryMethod queryMethod = new ArangoQueryMethod(method, metadata, factory); + final String namedQueryName = queryMethod.getNamedQueryName(); + + if (namedQueries.hasQuery(namedQueryName)) { + final String namedQuery = namedQueries.getQuery(namedQueryName); + return new StringBasedArangoQuery(namedQuery, queryMethod, operations); + } else if (queryMethod.hasAnnotatedQuery()) { + return new StringBasedArangoQuery(queryMethod, operations); + } else { + return new DerivedArangoQuery(queryMethod, operations); + } + } + } } diff --git a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java new file mode 100644 index 000000000..1585769b0 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java @@ -0,0 +1,173 @@ +/* + * 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.query; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.data.geo.GeoPage; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.util.Assert; + +import com.arangodb.ArangoCursor; +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.core.ArangoOperations; + +/** + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner + */ +public abstract class AbstractArangoQuery implements RepositoryQuery { + + protected final ArangoQueryMethod method; + protected final ArangoOperations operations; + protected final Class domainClass; + + public AbstractArangoQuery(ArangoQueryMethod method, ArangoOperations operations) { + Assert.notNull(method, "ArangoQueryMethod must not be null!"); + Assert.notNull(operations, "ArangoOperations must not be null!"); + this.method = method; + this.operations = operations; + this.domainClass = method.getEntityInformation().getJavaType(); + } + + @Override + public Object execute(Object[] parameters) { + final ArangoParameterAccessor accessor = new ArangoParametersParameterAccessor(method, parameters); + final Map bindVars = new HashMap<>(); + + AqlQueryOptions options = mergeQueryOptions(method.getAnnotatedQueryOptions(), accessor.getQueryOptions()); + if (options == null) { + options = new AqlQueryOptions(); + } + + if (method.isPageQuery() || GeoPage.class.equals(method.getReturnType())) { + options.fullCount(true); + } + + final String query = createQuery(accessor, bindVars, options); + final ArangoCursor result = operations.query(query, bindVars, options, getResultClass()); + return convertResult(result, accessor); + } + + @Override + public ArangoQueryMethod getQueryMethod() { + return method; + } + + /** + * Implementations should create an AQL query with the given + * {@link com.arangodb.springframework.repository.query.ArangoParameterAccessor} and set necessary binding + * parameters and query options. + * + * @param accessor + * provides access to the actual arguments + * @param bindVars + * the binding parameter map + * @param options + * contains the merged {@link com.arangodb.model.AqlQueryOptions} + * @return the created AQL query + */ + protected abstract String createQuery( + ArangoParameterAccessor accessor, + Map bindVars, + AqlQueryOptions options); + + protected abstract boolean isCountQuery(); + + protected abstract boolean isExistsQuery(); + + /** + * Merges AqlQueryOptions derived from @QueryOptions with dynamically passed AqlQueryOptions which takes priority + * + * @param oldStatic + * @param newDynamic + * @return + */ + protected AqlQueryOptions mergeQueryOptions(final AqlQueryOptions oldStatic, final AqlQueryOptions newDynamic) { + if (oldStatic == null) { + return newDynamic; + } + if (newDynamic == null) { + return oldStatic; + } + final Integer batchSize = newDynamic.getBatchSize(); + if (batchSize != null) { + oldStatic.batchSize(batchSize); + } + final Integer maxPlans = newDynamic.getMaxPlans(); + if (maxPlans != null) { + oldStatic.maxPlans(maxPlans); + } + final Integer ttl = newDynamic.getTtl(); + if (ttl != null) { + oldStatic.ttl(ttl); + } + final Boolean cache = newDynamic.getCache(); + if (cache != null) { + oldStatic.cache(cache); + } + final Boolean count = newDynamic.getCount(); + if (count != null) { + oldStatic.count(count); + } + final Boolean fullCount = newDynamic.getFullCount(); + if (fullCount != null) { + oldStatic.fullCount(fullCount); + } + final Boolean profile = newDynamic.getProfile(); + if (profile != null) { + oldStatic.profile(profile); + } + final Collection rules = newDynamic.getRules(); + if (rules != null) { + oldStatic.rules(rules); + } + return oldStatic; + } + + private Class getResultClass() { + if (isExistsQuery()) { + return Integer.class; + } + if (method.isGeoQuery()) { + return Object.class; + } + return method.getReturnedObjectType(); + } + + private Object convertResult(final ArangoCursor result, ArangoParameterAccessor accessor) { + if (isExistsQuery()) { + if (!result.hasNext()) { + return false; + } + return Integer.valueOf(result.next().toString()) > 0; + } + final ArangoResultConverter resultConverter = new ArangoResultConverter(accessor, result, operations, + domainClass); + return resultConverter.convertResult(method.getReturnType()); + } + +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java deleted file mode 100644 index ae4fa4924..000000000 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoAqlQuery.java +++ /dev/null @@ -1,412 +0,0 @@ -/* - * 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.repository.query; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.data.domain.Page; -import org.springframework.data.geo.GeoPage; -import org.springframework.data.geo.GeoResult; -import org.springframework.data.geo.GeoResults; -import org.springframework.data.projection.ProjectionFactory; -import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.query.QueryMethod; -import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.data.repository.query.parser.PartTree; -import org.springframework.util.Assert; - -import com.arangodb.ArangoCursor; -import com.arangodb.entity.BaseDocument; -import com.arangodb.entity.BaseEdgeDocument; -import com.arangodb.entity.IndexType; -import com.arangodb.model.AqlQueryOptions; -import com.arangodb.springframework.annotation.BindVars; -import com.arangodb.springframework.annotation.Param; -import com.arangodb.springframework.annotation.Query; -import com.arangodb.springframework.annotation.QueryOptions; -import com.arangodb.springframework.core.ArangoOperations; -import com.arangodb.springframework.core.mapping.ArangoMappingContext; -import com.arangodb.springframework.repository.query.derived.DerivedQueryCreator; -import com.arangodb.velocypack.VPackSlice; - -/** - * Implements execute(Object[]) method which is called every time a user-defined AQL or derived method is called - */ -public class ArangoAqlQuery implements RepositoryQuery { - - private static final Set> GEO_RETURN_TYPES = new HashSet<>(); - private static final Set> DESERIALIZABLE_TYPES = new HashSet<>(); - - static { - GEO_RETURN_TYPES.add(GeoResult.class); - GEO_RETURN_TYPES.add(GeoResults.class); - GEO_RETURN_TYPES.add(GeoPage.class); - - DESERIALIZABLE_TYPES.add(Map.class); - DESERIALIZABLE_TYPES.add(BaseDocument.class); - DESERIALIZABLE_TYPES.add(BaseEdgeDocument.class); - DESERIALIZABLE_TYPES.add(String.class); - DESERIALIZABLE_TYPES.add(Boolean.class); - DESERIALIZABLE_TYPES.add(boolean.class); - DESERIALIZABLE_TYPES.add(Integer.class); - DESERIALIZABLE_TYPES.add(int.class); - DESERIALIZABLE_TYPES.add(Long.class); - DESERIALIZABLE_TYPES.add(long.class); - DESERIALIZABLE_TYPES.add(Short.class); - DESERIALIZABLE_TYPES.add(short.class); - DESERIALIZABLE_TYPES.add(Double.class); - DESERIALIZABLE_TYPES.add(double.class); - DESERIALIZABLE_TYPES.add(Float.class); - DESERIALIZABLE_TYPES.add(float.class); - DESERIALIZABLE_TYPES.add(BigInteger.class); - DESERIALIZABLE_TYPES.add(BigDecimal.class); - DESERIALIZABLE_TYPES.add(Number.class); - DESERIALIZABLE_TYPES.add(Character.class); - DESERIALIZABLE_TYPES.add(char.class); - DESERIALIZABLE_TYPES.add(Date.class); - DESERIALIZABLE_TYPES.add(java.sql.Date.class); - DESERIALIZABLE_TYPES.add(java.sql.Timestamp.class); - DESERIALIZABLE_TYPES.add(VPackSlice.class); - DESERIALIZABLE_TYPES.add(UUID.class); - DESERIALIZABLE_TYPES.add(byte[].class); - DESERIALIZABLE_TYPES.add(Byte.class); - DESERIALIZABLE_TYPES.add(byte.class); - DESERIALIZABLE_TYPES.add(Enum.class); - DESERIALIZABLE_TYPES.add(Instant.class); - DESERIALIZABLE_TYPES.add(LocalDate.class); - DESERIALIZABLE_TYPES.add(LocalDateTime.class); - DESERIALIZABLE_TYPES.add(OffsetDateTime.class); - DESERIALIZABLE_TYPES.add(ZonedDateTime.class); - } - - private static final Logger LOGGER = LoggerFactory.getLogger(ArangoAqlQuery.class); - - private final ArangoOperations operations; - private final Class domainClass; - private final Method method; - private final RepositoryMetadata metadata; - private ArangoParameterAccessor accessor; - private boolean isCountProjection = false; - private boolean isExistsProjection = false; - private final ProjectionFactory factory; - - public ArangoAqlQuery(final Class domainClass, final Method method, final RepositoryMetadata metadata, - final ArangoOperations operations, final ProjectionFactory factory) { - this.domainClass = domainClass; - this.method = method; - this.metadata = metadata; - this.operations = operations; - this.factory = factory; - } - - @Override - public QueryMethod getQueryMethod() { - return new ArangoQueryMethod(method, metadata, factory); - } - - /** - * This method contains main logic showing how all user-defined methods are implemented - * - * @param arguments - * @return - */ - @SuppressWarnings("unchecked") - @Override - public Object execute(final Object[] arguments) { - Map bindVars = new HashMap<>(); - String query = getQueryAnnotationValue(); - AqlQueryOptions options = getAqlQueryOptions(); - boolean optionsFound = false; - if (query == null) { // derived method - final PartTree tree = new PartTree(method.getName(), domainClass); - isCountProjection = tree.isCountProjection(); - isExistsProjection = tree.isExistsProjection(); - accessor = new ArangoParameterAccessor(new ArangoParameters(method), arguments); - options = updateAqlQueryOptions(options, accessor.getAqlQueryOptions()); - if (Page.class.isAssignableFrom(method.getReturnType())) { - if (options == null) { - options = new AqlQueryOptions().fullCount(true); - } else { - options = options.fullCount(true); - } - } - final List geoFields = new LinkedList<>(); - if (GEO_RETURN_TYPES.contains(method.getReturnType())) { - operations.collection( - operations.getConverter().getMappingContext().getPersistentEntity(domainClass).getCollection()) - .getIndexes().forEach(i -> { - if ((i.getType() == IndexType.geo1) && geoFields.isEmpty()) { - i.getFields().forEach(f -> geoFields.add(f)); - } - }); - } - query = new DerivedQueryCreator((ArangoMappingContext) operations.getConverter().getMappingContext(), - domainClass, tree, accessor, bindVars, geoFields, - operations.getVersion().getVersion().compareTo("3.2.0") < 0).createQuery(); - } else if (arguments != null) { // AQL query method - final Set bindings = getBindings(query); - final Annotation[][] annotations = method.getParameterAnnotations(); - Assert.isTrue(arguments.length == annotations.length, "arguments.length != annotations.length"); - final Map bindVarsLocal = new HashMap<>(); - boolean bindVarsFound = false; - for (int i = 0; i < arguments.length; ++i) { - if (arguments[i] instanceof AqlQueryOptions) { - Assert.isTrue(!optionsFound, "AqlQueryOptions are already set"); - optionsFound = true; - options = updateAqlQueryOptions(options, (AqlQueryOptions) arguments[i]); - continue; - } - String parameter = null; - final Annotation specialAnnotation = getSpecialAnnotation(annotations[i]); - if (specialAnnotation != null) { - if (specialAnnotation.annotationType() == Param.class) { - parameter = ((Param) specialAnnotation).value(); - } else if (specialAnnotation.annotationType() == BindVars.class) { - Assert.isTrue(arguments[i] instanceof Map, "@BindVars must be a Map"); - Assert.isTrue(!bindVarsFound, "@BindVars duplicated"); - bindVars = (Map) arguments[i]; - bindVarsFound = true; - continue; - } - } - if (parameter == null) { - final String key = String.format("%d", i); - if (bindings.contains(key)) { - Assert.isTrue(!bindVarsLocal.containsKey(key), "duplicate parameter name"); - bindVarsLocal.put(key, arguments[i]); - } else if (bindings.contains("@" + key)) { - Assert.isTrue(!bindVarsLocal.containsKey("@" + key), "duplicate parameter name"); - bindVarsLocal.put("@" + key, arguments[i]); - } else { - LOGGER.debug("Local parameter '@{}' is not used in the query", key); - } - } else { - Assert.isTrue(!bindVarsLocal.containsKey(parameter), "duplicate parameter name"); - bindVarsLocal.put(parameter, arguments[i]); - } - } - mergeBindVars(bindVars, bindVarsLocal); - } - return convertResult(operations.query(query, bindVars, options, getResultClass())); - } - - /** - * Merges AqlQueryOptions derived from @QueryOptions with dynamically passed AqlQueryOptions which takes priority - * - * @param oldStatic - * @param newDynamic - * @return - */ - private AqlQueryOptions updateAqlQueryOptions(final AqlQueryOptions oldStatic, final AqlQueryOptions newDynamic) { - if (oldStatic == null) { - return newDynamic; - } - final Integer batchSize = newDynamic.getBatchSize(); - if (batchSize != null) { - oldStatic.batchSize(batchSize); - } - final Integer maxPlans = newDynamic.getMaxPlans(); - if (maxPlans != null) { - oldStatic.maxPlans(maxPlans); - } - final Integer ttl = newDynamic.getTtl(); - if (ttl != null) { - oldStatic.ttl(ttl); - } - final Boolean cache = newDynamic.getCache(); - if (cache != null) { - oldStatic.cache(cache); - } - final Boolean count = newDynamic.getCount(); - if (count != null) { - oldStatic.count(count); - } - final Boolean fullCount = newDynamic.getFullCount(); - if (fullCount != null) { - oldStatic.fullCount(fullCount); - } - final Boolean profile = newDynamic.getProfile(); - if (profile != null) { - oldStatic.profile(profile); - } - final Collection rules = newDynamic.getRules(); - if (rules != null) { - oldStatic.rules(rules); - } - return oldStatic; - } - - private AqlQueryOptions getAqlQueryOptions() { - final QueryOptions queryOptions = method.getAnnotation(QueryOptions.class); - if (queryOptions == null) { - return null; - } - final AqlQueryOptions options = new AqlQueryOptions(); - final int batchSize = queryOptions.batchSize(); - if (batchSize != -1) { - options.batchSize(batchSize); - } - final int maxPlans = queryOptions.maxPlans(); - if (maxPlans != -1) { - options.maxPlans(maxPlans); - } - final int ttl = queryOptions.ttl(); - if (ttl != -1) { - options.ttl(ttl); - } - options.cache(queryOptions.cache()); - options.count(queryOptions.count()); - options.fullCount(queryOptions.fullCount()); - options.profile(queryOptions.profile()); - options.rules(Arrays.asList(queryOptions.rules())); - return options; - } - - private Class getResultClass() { - if (isCountProjection || isExistsProjection) { - return Integer.class; - } - if (GEO_RETURN_TYPES.contains(method.getReturnType())) { - return Object.class; - } - if (DESERIALIZABLE_TYPES.contains(method.getReturnType())) { - return method.getReturnType(); - } - return domainClass; - } - - /** - * Returns Param or BindVars Annotation if it is present in the given array or null otherwise - * - * @param annotations - * @return - */ - private Annotation getSpecialAnnotation(final Annotation[] annotations) { - Annotation specialAnnotation = null; - for (final Annotation annotation : annotations) { - if (annotation.annotationType() == BindVars.class || annotation.annotationType() == Param.class) { - Assert.isTrue(specialAnnotation == null, "@BindVars or @Param should be used only once per parameter"); - specialAnnotation = annotation; - } - } - return specialAnnotation; - } - - private String getQueryAnnotationValue() { - final Query query = method.getAnnotation(Query.class); - return query == null ? null : query.value(); - } - - /** - * Merges bindVars Map passed by a user with a Map created from the rest of the arguments which take priority - * - * @param bindVars - * @param bindVarsLocal - */ - private void mergeBindVars(final Map bindVars, final Map bindVarsLocal) { - for (final String key : bindVarsLocal.keySet()) { - if (bindVars.containsKey(key)) { - LOGGER.debug("Local parameter '{}' overrides @BindVars Map", key); - } - bindVars.put(key, bindVarsLocal.get(key)); - } - } - - private Object convertResult(final ArangoCursor result) { - if (isExistsProjection) { - if (!result.hasNext()) { - return false; - } - return Integer.valueOf(result.next().toString()) > 0; - } - final ArangoResultConverter resultConverter = new ArangoResultConverter(accessor, result, operations, - domainClass); - return resultConverter.convertResult(method.getReturnType()); - } - - private String removeAqlStringLiterals(final String query) { - final StringBuilder fixedQuery = new StringBuilder(); - for (int i = 0; i < query.length(); ++i) { - if (query.charAt(i) == '"') { - for (++i; i < query.length(); ++i) { - if (query.charAt(i) == '"') { - ++i; - break; - } - if (query.charAt(i) == '\\') { - ++i; - } - } - } else if (query.charAt(i) == '\'') { - for (++i; i < query.length(); ++i) { - if (query.charAt(i) == '\'') { - ++i; - break; - } - if (query.charAt(i) == '\\') { - ++i; - } - } - } - fixedQuery.append(query.charAt(i)); - } - return fixedQuery.toString(); - } - - /** - * Returns all bindings used in AQL query String including bindings prefixed with both single and double '@' - * character ignoring AQL string literals - * - * @param query - * @return - */ - private Set getBindings(final String query) { - final String fixedQuery = removeAqlStringLiterals(query); - final Set bindings = new HashSet<>(); - final Matcher matcher = Pattern.compile("@\\S+").matcher(fixedQuery); - while (matcher.find()) { - bindings.add(matcher.group().substring(1)); - } - return bindings; - } -} diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoParameterAccessor.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoParameterAccessor.java index 0eac6522d..5277ad3d4 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoParameterAccessor.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoParameterAccessor.java @@ -1,7 +1,7 @@ /* * DISCLAIMER * - * Copyright 2017 ArangoDB GmbH, Cologne, Germany + * 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. @@ -20,77 +20,22 @@ package com.arangodb.springframework.repository.query; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; +import java.util.Map; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.ParameterAccessor; -import org.springframework.data.repository.query.ParametersParameterAccessor; -import org.springframework.util.Assert; import com.arangodb.model.AqlQueryOptions; /** - * The main class used to access parameters of a user-defined method. It wraps ParametersParameterAccessor which catches - * special parameters Sort and Pageable, and catches Arango-specific parameters e.g. AqlQueryOptions + * + * @author Christian Lechner */ -public class ArangoParameterAccessor implements ParameterAccessor { - private final ParametersParameterAccessor accessor; - private final List bindableArguments; - private AqlQueryOptions options = null; - - public ArangoParameterAccessor(final ArangoParameters parameters, final Object[] arguments) { - accessor = new ParametersParameterAccessor(parameters, arguments); - this.bindableArguments = createBindableArguments(arguments); - } - - AqlQueryOptions getAqlQueryOptions() { - return options; - } - - @Override - public Pageable getPageable() { - return accessor.getPageable(); - } - - @Override - public Sort getSort() { - return accessor.getSort(); - } - - @Override - public Class getDynamicProjection() { - return accessor.getDynamicProjection(); - } +public interface ArangoParameterAccessor extends ParameterAccessor { - @Override - public Object getBindableValue(final int index) { - return accessor.getBindableValue(index); - } + ArangoParameters getParameters(); - @Override - public boolean hasBindableNullValue() { - return accessor.hasBindableNullValue(); - } + AqlQueryOptions getQueryOptions(); - @Override - public Iterator iterator() { - return bindableArguments.iterator(); - } + Map getBindVars(); - private List createBindableArguments(final Object[] arguments) { - final List bindableArguments = new LinkedList<>(); - for (final Parameter parameter : accessor.getParameters().getBindableParameters()) { - if (parameter.getType() == AqlQueryOptions.class) { - Assert.isTrue(options == null, "AqlQueryOptions duplicated"); - options = (AqlQueryOptions) arguments[parameter.getIndex()]; - } else { - bindableArguments.add(arguments[parameter.getIndex()]); - } - } - return bindableArguments; - } } diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java index cec4bd6ed..afde248a1 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java @@ -21,23 +21,53 @@ package com.arangodb.springframework.repository.query; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Pattern; import org.springframework.core.MethodParameter; +import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; +import org.springframework.util.Assert; + +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.annotation.BindVars; /** - * Created by F625633 on 12/07/2017. + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner */ public class ArangoParameters extends Parameters { + private final int queryOptionsIndex; + private final int bindVarsIndex; + public ArangoParameters(final Method method) { super(method); + assertSingleSpecialParameter(ArangoParameter::isQueryOptions, + "Multiple AqlQueryOptions parameters are not allowed! Offending method: " + method); + assertSingleSpecialParameter(ArangoParameter::isBindVars, + "Multiple @BindVars parameters are not allowed! Offending method: " + method); + assertNonDuplicateParamNames(method); + this.queryOptionsIndex = getIndexOfSpecialParameter(ArangoParameter::isQueryOptions); + this.bindVarsIndex = getIndexOfSpecialParameter(ArangoParameter::isBindVars); } - public ArangoParameters(final List parameters) { + private ArangoParameters(final List parameters, final int queryOptionsIndex, + final int bindVarsIndex) { super(parameters); + this.queryOptionsIndex = queryOptionsIndex; + this.bindVarsIndex = bindVarsIndex; } @Override @@ -47,12 +77,146 @@ protected ArangoParameter createParameter(final MethodParameter parameter) { @Override protected ArangoParameters createFrom(final List parameters) { - return new ArangoParameters(parameters); + return new ArangoParameters(parameters, this.queryOptionsIndex, this.bindVarsIndex); + } + + public boolean hasQueryOptions() { + return this.queryOptionsIndex != -1; + } + + public int getQueryOptionsIndex() { + return this.queryOptionsIndex; + } + + public boolean hasBindVars() { + return this.bindVarsIndex != -1; } - protected static class ArangoParameter extends Parameter { + public int getBindVarsIndex() { + return this.bindVarsIndex; + } + + private int getIndexOfSpecialParameter(final Predicate condition) { + for (int index = 0; index < getNumberOfParameters(); ++index) { + final ArangoParameter param = getParameter(index); + if (condition.test(param)) { + return index; + } + } + return -1; + } + + private void assertSingleSpecialParameter(final Predicate condition, final String message) { + boolean found = false; + for (int index = 0; index < getNumberOfParameters(); ++index) { + final ArangoParameter param = getParameter(index); + if (condition.test(param)) { + Assert.isTrue(!found, message); + found = true; + } + } + } + + private void assertNonDuplicateParamNames(final Method method) { + final ArangoParameters bindableParams = getBindableParameters(); + final int bindableParamsSize = bindableParams.getNumberOfParameters(); + final Set paramNames = new HashSet<>(bindableParamsSize); + for (int i = 0; i < bindableParamsSize; ++i) { + final ArangoParameter param = bindableParams.getParameter(i); + final String name = param.getName(); + if (name != null) { + Assert.isTrue(!paramNames.contains(name), "Duplicate parameter name! Offending method: " + method); + paramNames.add(name); + } + } + } + + static class ArangoParameter extends Parameter { + + private static final Pattern BIND_PARAM_PATTERN = Pattern.compile("^@?[A-Za-z0-9][A-Za-z0-9_]*$"); + private static final String NAMED_PARAMETER_TEMPLATE = "@%s"; + private static final String POSITION_PARAMETER_TEMPLATE = "@%d"; + + private final MethodParameter parameter; + public ArangoParameter(final MethodParameter parameter) { super(parameter); + this.parameter = parameter; + assertCorrectBindParamPattern(); + assertCorrectBindVarsType(); } + + @Override + public boolean isSpecialParameter() { + return super.isSpecialParameter() || isQueryOptions() || isBindVars(); + } + + public boolean isQueryOptions() { + return AqlQueryOptions.class.isAssignableFrom(parameter.getParameterType()); + } + + public boolean isBindVars() { + return parameter.hasParameterAnnotation(BindVars.class); + } + + @SuppressWarnings("deprecation") + @Override + public String getName() { + Param annotation = parameter.getParameterAnnotation(Param.class); + // we need to support the old @Param annotation + com.arangodb.springframework.annotation.Param oldAnnotation = parameter + .getParameterAnnotation(com.arangodb.springframework.annotation.Param.class); + return annotation == null ? (oldAnnotation == null ? null : oldAnnotation.value()) : annotation.value(); + } + + @SuppressWarnings("deprecation") + @Override + public boolean isExplicitlyNamed() { + // we need to support the old @Param annotation + return super.isExplicitlyNamed() + || parameter.hasMethodAnnotation(com.arangodb.springframework.annotation.Param.class); + } + + @Override + public String getPlaceholder() { + if (isNamedParameter()) { + return String.format(NAMED_PARAMETER_TEMPLATE, getName()); + } else { + return String.format(POSITION_PARAMETER_TEMPLATE, getIndex()); + } + } + + private void assertCorrectBindParamPattern() { + if (isExplicitlyNamed()) { + final String name = getName(); + final boolean matches = BIND_PARAM_PATTERN.matcher(name).matches(); + Assert.isTrue(matches, "@Param has invalid format! Offending parameter: parameter " + + parameter.getParameterIndex() + " on method " + parameter.getMethod()); + } + } + + private void assertCorrectBindVarsType() { + final String errorMsg = "@BindVars parameter must be of type Map! Offending parameter: parameter " + + parameter.getParameterIndex() + " on method " + parameter.getMethod(); + + if (isBindVars()) { + Assert.isTrue(Map.class.equals(parameter.getParameterType()), errorMsg); + + final Type type = parameter.getGenericParameterType(); + Assert.isTrue(ParameterizedType.class.isInstance(type), errorMsg); + + final Type[] genericTypes = ((ParameterizedType) type).getActualTypeArguments(); + Assert.isTrue(genericTypes.length == 2, errorMsg); + + final Type keyType = genericTypes[0]; + final Type valueType = genericTypes[1]; + + Assert.isTrue(Class.class.isInstance(keyType), errorMsg); + Assert.isTrue(Class.class.isInstance(valueType), errorMsg); + Assert.isTrue(String.class.equals(keyType), errorMsg); + Assert.isTrue(Object.class.equals(valueType), errorMsg); + } + } + } } diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoParametersParameterAccessor.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoParametersParameterAccessor.java new file mode 100644 index 000000000..24818431b --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoParametersParameterAccessor.java @@ -0,0 +1,63 @@ +/* + * 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.repository.query; + +import java.util.Map; + +import org.springframework.data.repository.query.ParametersParameterAccessor; + +import com.arangodb.model.AqlQueryOptions; + +/** + * This class provides access to parameters of a user-defined method. It wraps ParametersParameterAccessor which catches + * special parameters Sort and Pageable, and catches Arango-specific parameters e.g. AqlQueryOptions. + * + * @author Audrius Malele + * @author Mark Vollmary + * @author Christian Lechner + */ +public class ArangoParametersParameterAccessor extends ParametersParameterAccessor implements ArangoParameterAccessor { + + private final ArangoParameters parameters; + + public ArangoParametersParameterAccessor(ArangoQueryMethod method, Object[] values) { + super(method.getParameters(), values); + this.parameters = method.getParameters(); + } + + @Override + public ArangoParameters getParameters() { + return parameters; + } + + @Override + public AqlQueryOptions getQueryOptions() { + final int optionsIndex = parameters.getQueryOptionsIndex(); + return optionsIndex == -1 ? null : getValue(optionsIndex); + } + + @Override + public Map getBindVars() { + final int bindVarsIndex = parameters.getBindVarsIndex(); + return bindVarsIndex == -1 ? null : getValue(bindVarsIndex); + } + +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryLookupStrategy.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryLookupStrategy.java deleted file mode 100644 index 73b0527be..000000000 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryLookupStrategy.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.repository.query; - -import java.lang.reflect.Method; - -import org.springframework.data.projection.ProjectionFactory; -import org.springframework.data.repository.core.NamedQueries; -import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.query.QueryLookupStrategy; -import org.springframework.data.repository.query.RepositoryQuery; - -import com.arangodb.springframework.core.ArangoOperations; - -/** - * Created by F625633 on 12/07/2017. - */ -public class ArangoQueryLookupStrategy implements QueryLookupStrategy { - - private final ArangoOperations operations; - - public ArangoQueryLookupStrategy(final ArangoOperations operations) { - this.operations = operations; - } - - @Override - public RepositoryQuery resolveQuery( - final Method method, - final RepositoryMetadata metadata, - final ProjectionFactory factory, - final NamedQueries namedQueries) { - return new ArangoAqlQuery(metadata.getDomainType(), method, metadata, operations, factory); - } -} diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java index a26c0a866..4d8b95fcb 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java @@ -21,28 +21,115 @@ package com.arangodb.springframework.repository.query; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.data.geo.GeoPage; +import org.springframework.data.geo.GeoResult; +import org.springframework.data.geo.GeoResults; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.QueryMethod; +import org.springframework.util.StringUtils; + +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.annotation.Query; +import com.arangodb.springframework.annotation.QueryOptions; /** - * Created by F625633 on 12/07/2017. + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner */ public class ArangoQueryMethod extends QueryMethod { + private static final List> GEO_TYPES = Arrays.asList(GeoResult.class, GeoResults.class, GeoPage.class); + + private final Method method; + public ArangoQueryMethod(final Method method, final RepositoryMetadata metadata, final ProjectionFactory factory) { super(method, metadata, factory); + this.method = method; } @Override - public Parameters getParameters() { - return super.getParameters(); + public ArangoParameters getParameters() { + return (ArangoParameters) super.getParameters(); } @Override - public Parameters createParameters(final Method method) { + public ArangoParameters createParameters(final Method method) { return new ArangoParameters(method); } + + public boolean hasAnnotatedQuery() { + return getQueryAnnotationValue().isPresent(); + } + + public String getAnnotatedQuery() { + return getQueryAnnotationValue().orElse(null); + } + + public Query getQueryAnnotation() { + return AnnotatedElementUtils.findMergedAnnotation(method, Query.class); + } + + private Optional getQueryAnnotationValue() { + return Optional.ofNullable(getQueryAnnotation()) // + .map(q -> q.value()) // + .filter(StringUtils::hasText); + } + + public boolean hasAnnotatedQueryOptions() { + return getQueryOptionsAnnotation() != null; + } + + public AqlQueryOptions getAnnotatedQueryOptions() { + final QueryOptions queryOptions = getQueryOptionsAnnotation(); + if (queryOptions == null) { + return null; + } + final AqlQueryOptions options = new AqlQueryOptions(); + final int batchSize = queryOptions.batchSize(); + if (batchSize != -1) { + options.batchSize(batchSize); + } + final int maxPlans = queryOptions.maxPlans(); + if (maxPlans != -1) { + options.maxPlans(maxPlans); + } + final int ttl = queryOptions.ttl(); + if (ttl != -1) { + options.ttl(ttl); + } + options.cache(queryOptions.cache()); + options.count(queryOptions.count()); + options.fullCount(queryOptions.fullCount()); + options.profile(queryOptions.profile()); + options.rules(Arrays.asList(queryOptions.rules())); + return options; + } + + public QueryOptions getQueryOptionsAnnotation() { + return AnnotatedElementUtils.findMergedAnnotation(method, QueryOptions.class); + } + + public Class getReturnType() { + return method.getReturnType(); + } + + public boolean isGeoQuery() { + final Class returnType = method.getReturnType(); + for (final Class type : GEO_TYPES) { + if (type.isAssignableFrom(returnType)) { + return true; + } + } + return false; + } + } diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoResultConverter.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoResultConverter.java index bea32eb74..beb57d6c8 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoResultConverter.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoResultConverter.java @@ -40,16 +40,25 @@ import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Metrics; +import org.springframework.util.Assert; import com.arangodb.ArangoCursor; import com.arangodb.springframework.core.ArangoOperations; import com.arangodb.springframework.core.convert.DBDocumentEntity; /** - * Class used to convert the result returned from the ArangoDB java driver from ArangoCursor to the desired type + * Converts the result returned from the ArangoDB Java driver to the desired type. + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner */ public class ArangoResultConverter { + private final static String MISSING_FULL_COUNT = "Query result does not contain the full result count! " + + "The most likely cause is a forgotten LIMIT clause in the query."; + private final ArangoParameterAccessor accessor; private final ArangoCursor result; private final ArangoOperations operations; @@ -202,6 +211,7 @@ public List convertList() { } public PageImpl convertPage() { + Assert.notNull(result.getStats().getFullCount(), MISSING_FULL_COUNT); return new PageImpl<>(result.asListRemaining(), accessor.getPageable(), result.getStats().getFullCount()); } @@ -222,6 +232,7 @@ public GeoResults convertGeoResults() { } public GeoPage convertGeoPage() { + Assert.notNull(result.getStats().getFullCount(), MISSING_FULL_COUNT); return new GeoPage<>(buildGeoResults(result), accessor.getPageable(), result.getStats().getFullCount()); } diff --git a/src/main/java/com/arangodb/springframework/repository/query/DerivedArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/DerivedArangoQuery.java new file mode 100644 index 000000000..040fd21d7 --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/DerivedArangoQuery.java @@ -0,0 +1,90 @@ +/* + * 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.query; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.repository.query.parser.PartTree; + +import com.arangodb.entity.IndexEntity; +import com.arangodb.entity.IndexType; +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.core.ArangoOperations; +import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; +import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; +import com.arangodb.springframework.repository.query.derived.DerivedQueryCreator; + +/** + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner + */ +public class DerivedArangoQuery extends AbstractArangoQuery { + + private final PartTree tree; + private final MappingContext, ArangoPersistentProperty> context; + private final List geoFields; + + public DerivedArangoQuery(ArangoQueryMethod method, ArangoOperations operations) { + super(method, operations); + this.tree = new PartTree(method.getName(), this.domainClass); + this.context = operations.getConverter().getMappingContext(); + this.geoFields = getGeoFields(); + } + + @Override + protected String createQuery( + final ArangoParameterAccessor accessor, + final Map bindVars, + final AqlQueryOptions options) { + + return new DerivedQueryCreator(context, domainClass, tree, accessor, bindVars, geoFields, + operations.getVersion().getVersion().compareTo("3.2.0") < 0).createQuery(); + } + + @Override + protected boolean isCountQuery() { + return tree.isCountProjection(); + } + + @Override + protected boolean isExistsQuery() { + return tree.isExistsProjection(); + } + + private List getGeoFields() { + final List geoFields = new LinkedList<>(); + if (method.isGeoQuery()) { + for (IndexEntity index : operations.collection(domainClass).getIndexes()) { + if ((index.getType() == IndexType.geo1)) { + geoFields.addAll(index.getFields()); + } + } + } + return geoFields; + } + +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java new file mode 100644 index 000000000..3abca36af --- /dev/null +++ b/src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java @@ -0,0 +1,141 @@ +/* + * 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.query; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.util.Assert; + +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.core.ArangoOperations; +import com.arangodb.springframework.repository.query.ArangoParameters.ArangoParameter; + +/** + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner + */ +public class StringBasedArangoQuery extends AbstractArangoQuery { + + private static final Pattern BIND_PARAM_PATTERN = Pattern.compile("@(@?[A-Za-z0-9][A-Za-z0-9_]*)"); + + private final String query; + private final Set queryBindParams; + + public StringBasedArangoQuery(ArangoQueryMethod method, ArangoOperations operations) { + this(method.getAnnotatedQuery(), method, operations); + } + + public StringBasedArangoQuery(String query, ArangoQueryMethod method, ArangoOperations operations) { + super(method, operations); + Assert.notNull(query, "Query must not be null!"); + + this.query = query; + this.queryBindParams = getBindParamsInQuery(); + } + + @Override + protected String createQuery(final ArangoParameterAccessor accessor, final Map bindVars, + final AqlQueryOptions options) { + + final Map bindVarsInParams = accessor.getBindVars(); + if (bindVarsInParams != null) { + bindVars.putAll(bindVarsInParams); + } + + final ArangoParameters bindableParams = accessor.getParameters().getBindableParameters(); + final int bindableParamsSize = bindableParams.getNumberOfParameters(); + + for (int i = 0; i < bindableParamsSize; ++i) { + final ArangoParameter param = bindableParams.getParameter(i); + final Object value = accessor.getBindableValue(i); + if (param.isNamedParameter()) { + bindVars.put(param.getName(), value); + } else { + final String key = String.valueOf(param.getIndex()); + final String collectionKey = "@" + key; + if (queryBindParams.contains(collectionKey)) { + bindVars.put(collectionKey, value); + } else { + bindVars.put(key, value); + } + } + } + + return query; + } + + @Override + protected boolean isCountQuery() { + return false; + } + + @Override + protected boolean isExistsQuery() { + return false; + } + + private Set getBindParamsInQuery() { + final String fixedQuery = removeAqlStringLiterals(query); + final Set bindings = new HashSet<>(); + final Matcher matcher = BIND_PARAM_PATTERN.matcher(fixedQuery); + while (matcher.find()) { + bindings.add(matcher.group(1)); + } + return bindings; + } + + private String removeAqlStringLiterals(final String query) { + final StringBuilder fixedQuery = new StringBuilder(); + for (int i = 0; i < query.length(); ++i) { + if (query.charAt(i) == '"') { + for (++i; i < query.length(); ++i) { + if (query.charAt(i) == '"') { + ++i; + break; + } + if (query.charAt(i) == '\\') { + ++i; + } + } + } else if (query.charAt(i) == '\'') { + for (++i; i < query.length(); ++i) { + if (query.charAt(i) == '\'') { + ++i; + break; + } + if (query.charAt(i) == '\\') { + ++i; + } + } + } + fixedQuery.append(query.charAt(i)); + } + return fixedQuery.toString(); + } + +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java index 8537525ce..0a3660124 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java +++ b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java @@ -26,6 +26,7 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -42,6 +43,7 @@ import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; import org.springframework.data.mapping.PropertyPath; +import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.PersistentPropertyPath; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.Part; @@ -49,7 +51,6 @@ import org.springframework.util.Assert; import com.arangodb.springframework.annotation.Relations; -import com.arangodb.springframework.core.mapping.ArangoMappingContext; import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; import com.arangodb.springframework.repository.query.ArangoParameterAccessor; @@ -74,7 +75,7 @@ public class DerivedQueryCreator extends AbstractQueryCreator, ArangoPersistentProperty> context; private final String collectionName; private final PartTree tree; private final Map bindVars; @@ -88,9 +89,10 @@ public class DerivedQueryCreator extends AbstractQueryCreator domainClass, final PartTree tree, - final ArangoParameterAccessor accessor, final Map bindVars, final List geoFields, - final boolean useFunctions) { + public DerivedQueryCreator( + final MappingContext, ArangoPersistentProperty> context, + final Class domainClass, final PartTree tree, final ArangoParameterAccessor accessor, + final Map bindVars, final List geoFields, final boolean useFunctions) { super(tree, accessor); this.context = context; this.collectionName = collectionName(context.getPersistentEntity(domainClass).getCollection()); @@ -141,9 +143,9 @@ protected ConjunctionBuilder or(final ConjunctionBuilder base, final Conjunction } /** - * Builds a full AQL query from a built Disjunction, additional information from PartTree and special parameters - * caught by ArangoParameterAccessor - * + * Builds a full AQL query from a built Disjunction, additional information from + * PartTree and special parameters caught by ArangoParameterAccessor + * * @param criteria * @param sort * @return @@ -158,28 +160,27 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { } final Disjunction disjunction = disjunctionBuilder.build(); final String array = disjunction.getArray().length() == 0 ? collectionName : disjunction.getArray(); - final String predicate = disjunction.getPredicate().length() == 0 ? "" - : " FILTER " + disjunction.getPredicate(); + final String predicate = disjunction.getPredicate().length() == 0 ? "" : " FILTER " + disjunction.getPredicate(); final String queryTemplate = "%sFOR e IN %s%s%s%s%s%s%s"; // collection predicate count sort limit pageable - // queryType + // queryType final String count = (tree.isCountProjection() || tree.isExistsProjection()) - ? ((tree.isDistinct() ? " COLLECT entity = e" : "") + " COLLECT WITH COUNT INTO length") : ""; - final String limit = tree.isLimiting() ? String.format(" LIMIT %d", tree.getMaxResults()) : ""; + ? ((tree.isDistinct() ? " COLLECT entity = e" : "") + " COLLECT WITH COUNT INTO length") + : ""; + final String limit = tree.isLimiting() ? format(" LIMIT %d", tree.getMaxResults()) : ""; final String pageable = accessor.getPageable() == null ? "" - : String.format(" LIMIT %d, %d", accessor.getPageable().getOffset(), - accessor.getPageable().getPageSize()); - final String geoFields = String.format("%s[0], %s[1]", uniqueLocation, uniqueLocation); + : format(" LIMIT %d, %d", accessor.getPageable().getOffset(), accessor.getPageable().getPageSize()); + final String geoFields = format("%s[0], %s[1]", uniqueLocation, uniqueLocation); final String distanceAdjusted = getGeoFields().isEmpty() ? "e" - : String.format("MERGE(e, { '_distance': distance(%s, %f, %f) })", geoFields, getUniquePoint()[0], - getUniquePoint()[1]); + : format("MERGE(e, { '_distance': distance(%s, %f, %f) })", geoFields, getUniquePoint()[0], + getUniquePoint()[1]); final String type = tree.isDelete() ? (" REMOVE e IN " + collectionName) : ((tree.isCountProjection() || tree.isExistsProjection()) ? " RETURN length" - : String.format(" RETURN %s", distanceAdjusted)); + : format(" RETURN %s", distanceAdjusted)); String sortString = buildSortString(sort); if ((!this.geoFields.isEmpty() || isUnique != null && isUnique) && !tree.isDelete() && !tree.isCountProjection() && !tree.isExistsProjection()) { - final String distanceSortKey = String.format(" SORT distance(%s, %f, %f)", geoFields, getUniquePoint()[0], - getUniquePoint()[1]); + final String distanceSortKey = format(" SORT distance(%s, %f, %f)", geoFields, getUniquePoint()[0], + getUniquePoint()[1]); if (sortString.length() == 0) { sortString = distanceSortKey; } else { @@ -189,13 +190,13 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { final String withCollections = disjunction.getWith().stream() .map(c -> collectionName(context.getPersistentEntity(c).getCollection())).distinct() .collect(Collectors.joining(", ")); - final String with = withCollections.isEmpty() ? "" : String.format("WITH %s ", withCollections); - return String.format(queryTemplate, with, array, predicate, count, sortString, limit, pageable, type); + final String with = withCollections.isEmpty() ? "" : format("WITH %s ", withCollections); + return format(queryTemplate, with, array, predicate, count, sortString, limit, pageable, type); } /** * Builds a String representing SORT statement from a given Sort object - * + * * @param sort * @return */ @@ -206,8 +207,8 @@ public static String buildSortString(final Sort sort) { final StringBuilder sortBuilder = new StringBuilder(sort == null ? "" : " SORT"); if (sort != null) { for (final Sort.Order order : sort) { - sortBuilder.append( - (sortBuilder.length() == 5 ? " " : ", ") + "e." + order.getProperty() + " " + order.getDirection()); + sortBuilder + .append((sortBuilder.length() == 5 ? " " : ", ") + "e." + order.getProperty() + " " + order.getDirection()); } } return sortBuilder.toString(); @@ -215,7 +216,7 @@ public static String buildSortString(final Sort sort) { /** * Escapes special characters which could be used in an operand of LIKE operator - * + * * @param string * @return */ @@ -236,8 +237,9 @@ private String ignorePropertyCase(final Part part) { } /** - * Wrapps property expression in order to lower case. Only properties of type String or Iterable are lowered - * + * Wrapps property expression in order to lower case. Only properties of type + * String or Iterable are lowered + * * @param part * @param property * @return @@ -249,25 +251,26 @@ private String ignorePropertyCase(final Part part, final String property) { if (!part.getProperty().getLeafProperty().isCollection()) { return "LOWER(" + property + ")"; } - return String.format("(FOR i IN TO_ARRAY(%s) RETURN LOWER(i))", property); + return format("(FOR i IN TO_ARRAY(%s) RETURN LOWER(i))", property); } /** * Returns a String representing a full propertyPath e.g. "e.product.name" - * + * * @param part * @return */ private String getProperty(final Part part) { - return "e." + context.getPersistentPropertyPath(part.getProperty()).toPath(null, - ArangoPersistentProperty::getFieldName); + return "e." + + context.getPersistentPropertyPath(part.getProperty()).toPath(null, ArangoPersistentProperty::getFieldName); } /** - * Creates a predicate template with one String placeholder for a Part-specific predicate expression from properties - * in PropertyPath which represent references or collections, and, also, returns a 2nd String representing property - * to be used in a Part-specific predicate expression - * + * Creates a predicate template with one String placeholder for a Part-specific + * predicate expression from properties in PropertyPath which represent + * references or collections, and, also, returns a 2nd String representing + * property to be used in a Part-specific predicate expression + * * @param part * @return */ @@ -290,7 +293,7 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { final String nested = simpleProperties.toString(); final Relations relations = property.getRelations().get(); final String direction = relations.direction().name(); - final String depths = String.format("%s..%d", relations.minDepth(), relations.maxDepth()); + final String depths = format("%s..%d", relations.minDepth(), relations.maxDepth()); final Class[] edgeClasses = relations.edges(); final StringBuilder edgesBuilder = new StringBuilder(); for (final Class edge : edgeClasses) { @@ -304,10 +307,9 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { final String entity = "e" + Integer.toString(++varsUsed); final String edges = edgesBuilder.toString(); simpleProperties = new StringBuilder(); - final String iteration = String.format(TEMPLATE, entity, depths, direction, prevEntity, nested, edges); - final String predicate = String.format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate - : String.format(predicateTemplate, predicate); + final String iteration = format(TEMPLATE, entity, depths, direction, prevEntity, nested, edges); + final String predicate = format(PREDICATE_TEMPLATE, iteration); + predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); } else if (property.isCollectionLike()) { if (property.getRef().isPresent()) { // collection of references @@ -320,10 +322,9 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { } final String name = simpleProperties.toString() + "." + property.getFieldName(); simpleProperties = new StringBuilder(); - final String iteration = String.format(TEMPLATE, entity, collection, entity, prevEntity, name); - final String predicate = String.format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate - : String.format(predicateTemplate, predicate); + final String iteration = format(TEMPLATE, entity, collection, entity, prevEntity, name); + final String predicate = format(PREDICATE_TEMPLATE, iteration); + predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); } else { // collection final String TEMPLATE = "FOR %s IN TO_ARRAY(%s%s)"; @@ -331,10 +332,9 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { final String entity = "e" + Integer.toString(++varsUsed); final String name = simpleProperties.toString() + "." + property.getFieldName(); simpleProperties = new StringBuilder(); - final String iteration = String.format(TEMPLATE, entity, prevEntity, name); - final String predicate = String.format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate - : String.format(predicateTemplate, predicate); + final String iteration = format(TEMPLATE, entity, prevEntity, name); + final String predicate = format(PREDICATE_TEMPLATE, iteration); + predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); } } else { if (property.getRef().isPresent() || property.getFrom().isPresent() || property.getTo().isPresent()) { @@ -348,10 +348,9 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { } final String name = simpleProperties.toString() + "." + property.getFieldName(); simpleProperties = new StringBuilder(); - final String iteration = String.format(TEMPLATE, entity, collection, entity, prevEntity, name); - final String predicate = String.format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate - : String.format(predicateTemplate, predicate); + final String iteration = format(TEMPLATE, entity, collection, entity, prevEntity, name); + final String predicate = format(PREDICATE_TEMPLATE, iteration); + predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); } else { // simple property simpleProperties.append("." + property.getFieldName()); @@ -363,8 +362,9 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { } /** - * Lowers case of a given argument if its type is String, Iterable or String[] if shouldIgnoreCase is true - * + * Lowers case of a given argument if its type is String, Iterable or + * String[] if shouldIgnoreCase is true + * * @param argument * @param shouldIgnoreCase * @return @@ -393,9 +393,9 @@ private Object ignoreArgumentCase(final Object argument, final boolean shouldIgn } /** - * Determines whether the case for a Part should be ignored based on property type and IgnoreCase keywords in the - * method name - * + * Determines whether the case for a Part should be ignored based on property + * type and IgnoreCase keywords in the method name + * * @param part * @return */ @@ -412,8 +412,9 @@ private boolean shouldIgnoreCase(final Part part) { } /** - * Puts actual arguments in bindVars Map based on Part-specific information and types of arguments. - * + * Puts actual arguments in bindVars Map based on Part-specific information and + * types of arguments. + * * @param iterator * @param shouldIgnoreCase * @param arguments @@ -421,12 +422,8 @@ private boolean shouldIgnoreCase(final Part part) { * @param ignoreBindVars * @return */ - private ArgumentProcessingResult bindArguments( - final Iterator iterator, - final boolean shouldIgnoreCase, - final int arguments, - final Boolean borderStatus, - final boolean ignoreBindVars) { + private ArgumentProcessingResult bindArguments(final Iterator iterator, final boolean shouldIgnoreCase, + final int arguments, final Boolean borderStatus, final boolean ignoreBindVars) { int bindings = 0; ArgumentProcessingResult.Type type = ArgumentProcessingResult.Type.DEFAULT; for (int i = 0; i < arguments; ++i) { @@ -472,8 +469,7 @@ private ArgumentProcessingResult bindArguments( checkUniquePoint(circle.getCenter()); bindVars.put(Integer.toString(bindingCounter + bindings++), circle.getCenter().getY()); bindVars.put(Integer.toString(bindingCounter + bindings++), circle.getCenter().getX()); - bindVars.put(Integer.toString(bindingCounter + bindings++), - convertDistanceToMeters(circle.getRadius())); + bindVars.put(Integer.toString(bindingCounter + bindings++), convertDistanceToMeters(circle.getRadius())); break; } else if (caseAdjusted.getClass() == Point.class) { final Point point = (Point) caseAdjusted; @@ -504,9 +500,9 @@ private ArgumentProcessingResult bindArguments( } /** - * Ensures that Points used in geospatial parts of non-nested properties are the same in case geospatial return type - * is expected - * + * Ensures that Points used in geospatial parts of non-nested properties are the + * same in case geospatial return type is expected + * * @param point */ private void checkUniquePoint(final Point point) { @@ -519,7 +515,7 @@ private void checkUniquePoint(final Point point) { } if (!geoFields.isEmpty()) { Assert.isTrue(uniquePoint == null || uniquePoint.equals(point), - "Different Points are used - Distance is ambiguous"); + "Different Points are used - Distance is ambiguous"); uniquePoint = point; } } @@ -541,9 +537,9 @@ private double convertDistanceToMeters(final Distance distance) { } /** - * Ensures that the same geo fields are used in geospatial parts of non-nested properties are the same in case - * geospatial return type is expected - * + * Ensures that the same geo fields are used in geospatial parts of non-nested + * properties are the same in case geospatial return type is expected + * * @param part */ private void checkUniqueLocation(final Part part) { @@ -559,9 +555,9 @@ private void checkUniqueLocation(final Part part) { } /** - * Creates a PartInformation containing a String representing either a predicate or array expression, and binds - * arguments from Iterator for a given Part - * + * Creates a PartInformation containing a String representing either a predicate + * or array expression, and binds arguments from Iterator for a given Part + * * @param part * @param iterator * @return @@ -582,121 +578,122 @@ private PartInformation createPartInformation(final Part part, final Iterator 1) { collectionName = "`" + collectionName + "`"; } - // TODO possibly refactor in the future if the complexity of this block does not increase + // TODO possibly refactor in the future if the complexity of this block does not + // increase switch (part.getType()) { case SIMPLE_PROPERTY: isArray = false; - clause = String.format("%s == @%d", ignorePropertyCase(part, property), bindingCounter); + clause = format("%s == @%d", ignorePropertyCase(part, property), bindingCounter); arguments = 1; break; case NEGATING_SIMPLE_PROPERTY: isArray = false; - clause = String.format("%s != @%d", ignorePropertyCase(part, property), bindingCounter); + clause = format("%s != @%d", ignorePropertyCase(part, property), bindingCounter); arguments = 1; break; case TRUE: isArray = false; - clause = String.format("%s == true", ignorePropertyCase(part, property)); + clause = format("%s == true", ignorePropertyCase(part, property)); break; case FALSE: isArray = false; - clause = String.format("%s == false", ignorePropertyCase(part, property)); + clause = format("%s == false", ignorePropertyCase(part, property)); break; case IS_NULL: isArray = false; - clause = String.format("%s == null", ignorePropertyCase(part, property)); + clause = format("%s == null", ignorePropertyCase(part, property)); break; case IS_NOT_NULL: isArray = false; - clause = String.format("%s != null", ignorePropertyCase(part, property)); + clause = format("%s != null", ignorePropertyCase(part, property)); break; case EXISTS: isArray = false; - clause = String.format("HAS(%s, '%s')", property.substring(0, property.lastIndexOf(".")), - property.substring(property.lastIndexOf(".") + 1, property.length())); + clause = format("HAS(%s, '%s')", property.substring(0, property.lastIndexOf(".")), + property.substring(property.lastIndexOf(".") + 1, property.length())); break; case BEFORE: case LESS_THAN: isArray = false; - clause = String.format("%s < @%d", ignorePropertyCase(part, property), bindingCounter); + clause = format("%s < @%d", ignorePropertyCase(part, property), bindingCounter); arguments = 1; break; case AFTER: case GREATER_THAN: isArray = false; - clause = String.format("%s > @%d", ignorePropertyCase(part, property), bindingCounter); + clause = format("%s > @%d", ignorePropertyCase(part, property), bindingCounter); arguments = 1; break; case LESS_THAN_EQUAL: isArray = false; - clause = String.format("%s <= @%d", ignorePropertyCase(part, property), bindingCounter); + clause = format("%s <= @%d", ignorePropertyCase(part, property), bindingCounter); arguments = 1; break; case GREATER_THAN_EQUAL: isArray = false; - clause = String.format("%s >= @%d", ignorePropertyCase(part, property), bindingCounter); + clause = format("%s >= @%d", ignorePropertyCase(part, property), bindingCounter); arguments = 1; break; case BETWEEN: isArray = false; - clause = String.format("@%d <= %s AND %s <= @%d", bindingCounter, ignorePropertyCase(part, property), - ignorePropertyCase(part, property), bindingCounter + 1); + clause = format("@%d <= %s AND %s <= @%d", bindingCounter, ignorePropertyCase(part, property), + ignorePropertyCase(part, property), bindingCounter + 1); arguments = 2; break; case LIKE: isArray = false; - clause = String.format("%s LIKE @%d", ignorePropertyCase(part, property), bindingCounter); + clause = format("%s LIKE @%d", ignorePropertyCase(part, property), bindingCounter); arguments = 1; break; case NOT_LIKE: isArray = false; - clause = String.format("NOT(%s LIKE @%d)", ignorePropertyCase(part, property), bindingCounter); + clause = format("NOT(%s LIKE @%d)", ignorePropertyCase(part, property), bindingCounter); arguments = 1; break; case STARTING_WITH: isArray = false; - clause = String.format("%s LIKE @%d", ignorePropertyCase(part, property), bindingCounter); + clause = format("%s LIKE @%d", ignorePropertyCase(part, property), bindingCounter); arguments = 1; borderStatus = true; break; case ENDING_WITH: isArray = false; - clause = String.format("%s LIKE @%d", ignorePropertyCase(part, property), bindingCounter); + clause = format("%s LIKE @%d", ignorePropertyCase(part, property), bindingCounter); arguments = 1; borderStatus = false; break; case REGEX: isArray = false; - clause = String.format("REGEX_TEST(%s, @%d, %b)", ignorePropertyCase(part, property), bindingCounter, - shouldIgnoreCase(part)); + clause = format("REGEX_TEST(%s, @%d, %b)", ignorePropertyCase(part, property), bindingCounter, + shouldIgnoreCase(part)); arguments = 1; break; case IN: isArray = false; - clause = String.format("%s IN @%d", ignorePropertyCase(part, property), bindingCounter); + clause = format("%s IN @%d", ignorePropertyCase(part, property), bindingCounter); arguments = 1; break; case NOT_IN: isArray = false; - clause = String.format("%s NOT IN @%d", ignorePropertyCase(part, property), bindingCounter); + clause = format("%s NOT IN @%d", ignorePropertyCase(part, property), bindingCounter); arguments = 1; break; case CONTAINING: isArray = false; - clause = String.format("@%d IN %s", bindingCounter, ignorePropertyCase(part, property)); + clause = format("@%d IN %s", bindingCounter, ignorePropertyCase(part, property)); arguments = 1; break; case NOT_CONTAINING: isArray = false; - clause = String.format("@%d NOT IN %s", bindingCounter, ignorePropertyCase(part, property)); + clause = format("@%d NOT IN %s", bindingCounter, ignorePropertyCase(part, property)); arguments = 1; break; case NEAR: checkUniqueLocation(part); if (useFunctions) { isArray = true; - clause = String.format("NEAR(%s, @%d, @%d, COUNT(%s), '_distance')", collectionName, bindingCounter, - bindingCounter + 1, collectionName); + clause = format("NEAR(%s, @%d, @%d, COUNT(%s), '_distance')", collectionName, bindingCounter, + bindingCounter + 1, collectionName); if (geoFields.isEmpty()) { clause = unsetDistance(clause); } @@ -709,60 +706,58 @@ private PartInformation createPartInformation(final Part part, final Iterator> with = new ArrayList<>(); PropertyPath pp = part.getProperty(); do { Optional.ofNullable(pp.isCollection() ? pp.getOwningType().getComponentType() : pp.getOwningType()) - .filter(t -> context.getPersistentEntity(t) != null).map(t -> t.getType()) - .ifPresent(t -> with.add(t)); + .filter(t -> context.getPersistentEntity(t) != null).map(t -> t.getType()).ifPresent(t -> with.add(t)); } while ((pp = pp.next()) != null); return clause == null ? null : new PartInformation(isArray, clause, with); } private String unsetDistance(final String clause) { - return String.format("(FOR u IN %s RETURN UNSET(u, '_distance'))", clause); + return format("(FOR u IN %s RETURN UNSET(u, '_distance'))", clause); + } + + private String format(final String format, final Object... args) { + return String.format(Locale.ENGLISH, format, args); } /** - * Stores how many bindings where used in a Part and if or what kind of special type clause should be created + * Stores how many bindings where used in a Part and if or what kind of special + * type clause should be created */ private static class ArgumentProcessingResult { diff --git a/src/test/java/com/arangodb/springframework/ArangoTestConfiguration.java b/src/test/java/com/arangodb/springframework/ArangoTestConfiguration.java index 09b220aad..f2f639b92 100644 --- a/src/test/java/com/arangodb/springframework/ArangoTestConfiguration.java +++ b/src/test/java/com/arangodb/springframework/ArangoTestConfiguration.java @@ -21,17 +21,18 @@ package com.arangodb.springframework; import org.springframework.context.annotation.Configuration; - import com.arangodb.ArangoDB; import com.arangodb.springframework.annotation.EnableArangoRepositories; import com.arangodb.springframework.config.AbstractArangoConfiguration; /** + * * @author Mark Vollmary - * + * @author Christian Lechner */ @Configuration -@EnableArangoRepositories(basePackages = { "com.arangodb.springframework.repository" }) +@EnableArangoRepositories(basePackages = { + "com.arangodb.springframework.repository" }, namedQueriesLocation = "classpath*:arango-named-queries-test.properties") public class ArangoTestConfiguration extends AbstractArangoConfiguration { public static final String DB = "spring-test-db"; diff --git a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java index 31669ed6a..2d3c9987f 100644 --- a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java +++ b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java @@ -4,7 +4,6 @@ import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import org.springframework.data.domain.Page; @@ -19,19 +18,23 @@ import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; +import org.springframework.data.repository.query.Param; import com.arangodb.ArangoCursor; import com.arangodb.entity.BaseDocument; import com.arangodb.model.AqlQueryOptions; import com.arangodb.springframework.annotation.BindVars; -import com.arangodb.springframework.annotation.Param; import com.arangodb.springframework.annotation.Query; import com.arangodb.springframework.annotation.QueryOptions; import com.arangodb.springframework.repository.query.derived.geo.Ring; import com.arangodb.springframework.testdata.Customer; /** - * Created by F625633 on 07/07/2017. + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner */ public interface CustomerRepository extends ArangoRepository { @@ -42,48 +45,33 @@ public interface CustomerRepository extends ArangoRepository { @QueryOptions(cache = true, ttl = 128) BaseDocument findOneByIdAndNameAql(String id, String name); - @Query("FOR c IN customer FILTER c._id == @0 RETURN c") - Optional findOneByIdAqlPotentialNameClash(@Param("0") String id); - - @Query("FOR c IN customer FILTER c._id == @0 AND c.name == @0 RETURN c") - ArangoCursor findOneByIdAqlParamNameClash(String id, @Param("0") String name); - @QueryOptions(maxPlans = 1000, ttl = 128) @Query("FOR c IN customer FILTER c._id == @id AND c.name == @name RETURN c") - ArangoCursor findOneByBindVarsAql( - AqlQueryOptions options, - @SuppressWarnings("rawtypes") @BindVars Map bindVars); + ArangoCursor findOneByBindVarsAql(AqlQueryOptions options, @BindVars Map bindVars); @Query("FOR c IN customer FILTER c._id == @id AND c.name == @name RETURN c") Customer findOneByNameAndBindVarsAql(@Param("name") String name, @BindVars Map bindVars); - @Query("FOR c IN customer FILTER c._id == @id AND c.name == @name RETURN c") - Customer findOneByBindVarsAndClashingParametersAql( - @BindVars Map bindVars, - @Param("name") String name, - AqlQueryOptions options, - @Param("name") String name2); - - @Query("FOR c IN customer FILTER c.name == @name RETURN c") - Customer findOneByNameWithDuplicateOptionsAql( - @Param("name") String name, - AqlQueryOptions options, - AqlQueryOptions options2); - @Query("FOR c IN customer FILTER c._id == @id AND c.name == @0 RETURN c") Customer findOneByIdAndNameWithBindVarsAql(String name, @BindVars Map bindVars); @Query("FOR c IN @@0 FILTER \"@1\" != '@2' AND c._id == @1 RETURN c") - Customer findOneByIdInCollectionAql(String collection, String id, String id2); + Customer findOneByIdInCollectionAqlWithUnusedParam(String collection, String id, String id2); - @Query("FOR c IN @@collection FILTER \"\\\"@1\\\"\" != '\"@2\"' AND c._id == @1 RETURN c") - Customer findOneByIdInNamedCollectionAql(@Param("@collection") String collection, String id, String id2); + @Query("FOR c IN @@collection FILTER \"\\\"@id\\\"\" != '\"@id2\"' AND c._id == @id RETURN c") + Customer findOneByIdInNamedCollectionAqlWithUnusedParam( + @Param("@collection") String collection, + @Param("id") String id, + @Param("id2") String id2); - @Query("FOR c IN @@collection FILTER \"'@1'\" != '\\'@2\\'' AND c._id == @1 RETURN c") - Customer findOneByIdInIncorrectNamedCollectionAql(@Param("collection") String collection, String id, String id2); + @Query("FOR c IN @@collection FILTER \"'@id'\" != '\\'@id2\\'' AND c._id == @id RETURN c") + Customer findOneByIdInIncorrectNamedCollectionAql( + @Param("collection") String collection, + @Param("id") String id, + @Param("id2") String id2); - @Query("FOR c IN @collection FILTER c._id == @1 RETURN c") - Customer findOneByIdInNamedCollectionAqlRejected(@Param("collection") String collection, String id); + @Query("FOR c IN @collection FILTER c._id == @id RETURN c") + Customer findOneByIdInNamedCollectionAqlRejected(@Param("collection") String collection, @Param("id") String id); @Query("FOR c in customer FILTER c.surname == @0 RETURN c") List findManyBySurname(String surname); @@ -199,9 +187,17 @@ List findByNestedCustomersNestedCustomerShoppingCartProductsLocationWi List getByOwnsContainsName(String name); + // Count query + @Query("RETURN COUNT(@@collection)") long queryCount(@Param("@collection") Class collection); + + // Date query @Query("RETURN DATE_ISO8601(1474988621)") Instant queryDate(); -} + + // Named query + + Customer findOneByIdNamedQuery(@Param("id") String id); +} \ No newline at end of file diff --git a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java index 84cb2b9d0..bc97914a9 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java @@ -8,7 +8,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Optional; import org.junit.Test; import org.junit.runner.RunWith; @@ -23,7 +22,11 @@ import com.arangodb.springframework.testdata.Customer; /** - * Created by F625633 on 12/07/2017. + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner */ @RunWith(SpringJUnit4ClassRunner.class) public class ArangoAqlQueryTest extends AbstractArangoRepositoryTest { @@ -34,8 +37,7 @@ public class ArangoAqlQueryTest extends AbstractArangoRepositoryTest { public void findOneByIdAqlWithNamedParameterTest() { repository.save(customers); final Map retrieved = repository.findOneByIdAqlWithNamedParameter(john.getId(), OPTIONS); - final Customer retrievedCustomer = template.getConverter().read(Customer.class, - new DBDocumentEntity(retrieved)); + final Customer retrievedCustomer = template.getConverter().read(Customer.class, new DBDocumentEntity(retrieved)); assertEquals(john, retrievedCustomer); } @@ -49,42 +51,17 @@ public void findOneByIdAndNameAqlTest() { allProperties.put("_rev", retrieved.getRevision()); retrieved.getProperties().forEach((k, v) -> allProperties.put(k, v)); final Customer retrievedCustomer = template.getConverter().read(Customer.class, - new DBDocumentEntity(allProperties)); + new DBDocumentEntity(allProperties)); assertEquals(john, retrievedCustomer); } - @Test - public void findOneByIdAqlPotentialNameClashTest() { - repository.save(customers); - final Optional retrieved = repository.findOneByIdAqlPotentialNameClash(john.getId()); - assertEquals(john, retrieved.get()); - } - - @Test(expected = IllegalArgumentException.class) - public void findOneByIdAqlParamNameClashTest() { - repository.save(customers); - final ArangoCursor retrieved = repository.findOneByIdAqlParamNameClash(john.getId(), john.getName()); - assertEquals(john, retrieved.next()); - } - @Test public void findOneByBindVarsAqlTest() { repository.save(customers); final Map bindVars = new HashMap<>(); bindVars.put("id", john.getId()); bindVars.put("name", john.getName()); - final ArangoCursor retrieved = repository.findOneByBindVarsAql(OPTIONS.ttl(127).cache(true), - bindVars); - assertEquals(john, retrieved.next()); - } - - @Test(expected = ClassCastException.class) - public void findOneByBindVarsOfIllegalTypeAqlTest() { - repository.save(customers); - final Map bindVars = new HashMap<>(); - bindVars.put(1, john.getId()); - bindVars.put(2, john.getName()); - final ArangoCursor retrieved = repository.findOneByBindVarsAql(OPTIONS, bindVars); + final ArangoCursor retrieved = repository.findOneByBindVarsAql(OPTIONS.ttl(127).cache(true), bindVars); assertEquals(john, retrieved.next()); } @@ -107,24 +84,6 @@ public void findOneByOverridingNameAndBindVarsAqlTest() { assertEquals(john, retrieved); } - @Test(expected = IllegalArgumentException.class) - public void findOneByBindVarsAndClashingParametersAqlTest() { - repository.save(customers); - final Map bindVars = new HashMap<>(); - bindVars.put("id", john.getId()); - bindVars.put("name", john.getName()); - final Customer retrieved = repository.findOneByBindVarsAndClashingParametersAql(bindVars, john.getName(), - OPTIONS, john.getName()); - assertEquals(john, retrieved); - } - - @Test(expected = IllegalArgumentException.class) - public void findOneByNameWithDuplicateOptionsAqlTest() { - repository.save(customers); - final Customer retrieved = repository.findOneByNameWithDuplicateOptionsAql(john.getName(), OPTIONS, OPTIONS); - assertEquals(john, retrieved); - } - @Test public void findOneByIdAndNameWithBindVarsAqlTest() { repository.save(customers); @@ -135,19 +94,19 @@ public void findOneByIdAndNameWithBindVarsAqlTest() { assertEquals(john, retrieved); } - @Test - public void findOneByIdInCollectionAqlTest() { + @Test(expected = ArangoDBException.class) + public void findOneByIdInCollectionAqlWithUnusedParamTest() { repository.save(customers); - final Customer retrieved = repository.findOneByIdInCollectionAql(john.getId().split("/")[0], john.getId(), - john.getId()); + final Customer retrieved = repository.findOneByIdInCollectionAqlWithUnusedParam(john.getId().split("/")[0], + john.getId(), john.getId()); assertEquals(john, retrieved); } - @Test - public void findOneByIdInNamedCollectionAqlTest() { + @Test(expected = ArangoDBException.class) + public void findOneByIdInNamedCollectionAqlWithUnusedParamTest() { repository.save(customers); - final Customer retrieved = repository.findOneByIdInNamedCollectionAql(john.getId().split("/")[0], john.getId(), - john.getId()); + final Customer retrieved = repository.findOneByIdInNamedCollectionAqlWithUnusedParam(john.getId().split("/")[0], + john.getId(), john.getId()); assertEquals(john, retrieved); } @@ -155,7 +114,7 @@ public void findOneByIdInNamedCollectionAqlTest() { public void findOneByIdInIncorrectNamedCollectionAqlTest() { repository.save(customers); final Customer retrieved = repository.findOneByIdInIncorrectNamedCollectionAql(john.getId().split("/")[0], - john.getId(), john.getId()); + john.getId(), john.getId()); assertEquals(john, retrieved); } @@ -163,7 +122,7 @@ public void findOneByIdInIncorrectNamedCollectionAqlTest() { public void findOneByIdInNamedCollectionAqlRejectedTest() { repository.save(customers); final Customer retrieved = repository.findOneByIdInNamedCollectionAqlRejected(john.getId().split("/")[0], - john.getId()); + john.getId()); assertEquals(john, retrieved); } @@ -187,4 +146,11 @@ public void queryCount() { public void queryDate() { assertEquals(repository.queryDate(), Instant.ofEpochMilli(1474988621)); } + + @Test + public void findOneByIdNamedQueryTest() { + repository.save(customers); + final Customer retrieved = repository.findOneByIdNamedQuery(john.getId()); + assertEquals(john, retrieved); + } } diff --git a/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java b/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java index b97d5d329..478eab829 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java @@ -8,6 +8,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Set; import org.junit.Test; @@ -744,8 +745,8 @@ private double convertAngleToDistance(final int angle) { } private double getDistanceBetweenPoints(final Point point1, final Point point2) { - final String query = String.format("RETURN DISTANCE(%f, %f, %f, %f)", point1.getY(), point1.getX(), - point2.getY(), point2.getX()); + final String query = String.format(Locale.ENGLISH, "RETURN DISTANCE(%f, %f, %f, %f)", point1.getY(), + point1.getX(), point2.getY(), point2.getX()); return template.query(query, new HashMap<>(), null, Double.class).next(); } } diff --git a/src/test/resources/arango-named-queries-test.properties b/src/test/resources/arango-named-queries-test.properties new file mode 100644 index 000000000..8ab67b3c1 --- /dev/null +++ b/src/test/resources/arango-named-queries-test.properties @@ -0,0 +1 @@ +Customer.findOneByIdNamedQuery = FOR c IN customer FILTER c._id == @id RETURN c \ No newline at end of file From 222558c47645e4f4b7b4701676dfc43284c19908 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 4 May 2018 09:06:08 +0200 Subject: [PATCH 34/94] Fix import --- .../repository/query/ArangoParameters.java | 443 +++++++++--------- 1 file changed, 221 insertions(+), 222 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java index afde248a1..6414bd4ec 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoParameters.java @@ -1,222 +1,221 @@ -/* - * 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.repository.query; - -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -import org.springframework.core.MethodParameter; -import org.springframework.data.repository.query.Param; -import org.springframework.data.repository.query.Parameter; -import org.springframework.data.repository.query.Parameters; -import org.springframework.util.Assert; - -import com.arangodb.model.AqlQueryOptions; -import com.arangodb.springframework.annotation.BindVars; - -/** - * - * @author Audrius Malele - * @author Mark McCormick - * @author Mark Vollmary - * @author Christian Lechner - */ -public class ArangoParameters extends Parameters { - - private final int queryOptionsIndex; - private final int bindVarsIndex; - - public ArangoParameters(final Method method) { - super(method); - assertSingleSpecialParameter(ArangoParameter::isQueryOptions, - "Multiple AqlQueryOptions parameters are not allowed! Offending method: " + method); - assertSingleSpecialParameter(ArangoParameter::isBindVars, - "Multiple @BindVars parameters are not allowed! Offending method: " + method); - assertNonDuplicateParamNames(method); - this.queryOptionsIndex = getIndexOfSpecialParameter(ArangoParameter::isQueryOptions); - this.bindVarsIndex = getIndexOfSpecialParameter(ArangoParameter::isBindVars); - } - - private ArangoParameters(final List parameters, final int queryOptionsIndex, - final int bindVarsIndex) { - super(parameters); - this.queryOptionsIndex = queryOptionsIndex; - this.bindVarsIndex = bindVarsIndex; - } - - @Override - protected ArangoParameter createParameter(final MethodParameter parameter) { - return new ArangoParameter(parameter); - } - - @Override - protected ArangoParameters createFrom(final List parameters) { - return new ArangoParameters(parameters, this.queryOptionsIndex, this.bindVarsIndex); - } - - public boolean hasQueryOptions() { - return this.queryOptionsIndex != -1; - } - - public int getQueryOptionsIndex() { - return this.queryOptionsIndex; - } - - public boolean hasBindVars() { - return this.bindVarsIndex != -1; - } - - public int getBindVarsIndex() { - return this.bindVarsIndex; - } - - private int getIndexOfSpecialParameter(final Predicate condition) { - for (int index = 0; index < getNumberOfParameters(); ++index) { - final ArangoParameter param = getParameter(index); - if (condition.test(param)) { - return index; - } - } - return -1; - } - - private void assertSingleSpecialParameter(final Predicate condition, final String message) { - boolean found = false; - for (int index = 0; index < getNumberOfParameters(); ++index) { - final ArangoParameter param = getParameter(index); - if (condition.test(param)) { - Assert.isTrue(!found, message); - found = true; - } - } - } - - private void assertNonDuplicateParamNames(final Method method) { - final ArangoParameters bindableParams = getBindableParameters(); - final int bindableParamsSize = bindableParams.getNumberOfParameters(); - final Set paramNames = new HashSet<>(bindableParamsSize); - for (int i = 0; i < bindableParamsSize; ++i) { - final ArangoParameter param = bindableParams.getParameter(i); - final String name = param.getName(); - if (name != null) { - Assert.isTrue(!paramNames.contains(name), "Duplicate parameter name! Offending method: " + method); - paramNames.add(name); - } - } - } - - static class ArangoParameter extends Parameter { - - private static final Pattern BIND_PARAM_PATTERN = Pattern.compile("^@?[A-Za-z0-9][A-Za-z0-9_]*$"); - private static final String NAMED_PARAMETER_TEMPLATE = "@%s"; - private static final String POSITION_PARAMETER_TEMPLATE = "@%d"; - - private final MethodParameter parameter; - - public ArangoParameter(final MethodParameter parameter) { - super(parameter); - this.parameter = parameter; - assertCorrectBindParamPattern(); - assertCorrectBindVarsType(); - } - - @Override - public boolean isSpecialParameter() { - return super.isSpecialParameter() || isQueryOptions() || isBindVars(); - } - - public boolean isQueryOptions() { - return AqlQueryOptions.class.isAssignableFrom(parameter.getParameterType()); - } - - public boolean isBindVars() { - return parameter.hasParameterAnnotation(BindVars.class); - } - - @SuppressWarnings("deprecation") - @Override - public String getName() { - Param annotation = parameter.getParameterAnnotation(Param.class); - // we need to support the old @Param annotation - com.arangodb.springframework.annotation.Param oldAnnotation = parameter - .getParameterAnnotation(com.arangodb.springframework.annotation.Param.class); - return annotation == null ? (oldAnnotation == null ? null : oldAnnotation.value()) : annotation.value(); - } - - @SuppressWarnings("deprecation") - @Override - public boolean isExplicitlyNamed() { - // we need to support the old @Param annotation - return super.isExplicitlyNamed() - || parameter.hasMethodAnnotation(com.arangodb.springframework.annotation.Param.class); - } - - @Override - public String getPlaceholder() { - if (isNamedParameter()) { - return String.format(NAMED_PARAMETER_TEMPLATE, getName()); - } else { - return String.format(POSITION_PARAMETER_TEMPLATE, getIndex()); - } - } - - private void assertCorrectBindParamPattern() { - if (isExplicitlyNamed()) { - final String name = getName(); - final boolean matches = BIND_PARAM_PATTERN.matcher(name).matches(); - Assert.isTrue(matches, "@Param has invalid format! Offending parameter: parameter " - + parameter.getParameterIndex() + " on method " + parameter.getMethod()); - } - } - - private void assertCorrectBindVarsType() { - final String errorMsg = "@BindVars parameter must be of type Map! Offending parameter: parameter " - + parameter.getParameterIndex() + " on method " + parameter.getMethod(); - - if (isBindVars()) { - Assert.isTrue(Map.class.equals(parameter.getParameterType()), errorMsg); - - final Type type = parameter.getGenericParameterType(); - Assert.isTrue(ParameterizedType.class.isInstance(type), errorMsg); - - final Type[] genericTypes = ((ParameterizedType) type).getActualTypeArguments(); - Assert.isTrue(genericTypes.length == 2, errorMsg); - - final Type keyType = genericTypes[0]; - final Type valueType = genericTypes[1]; - - Assert.isTrue(Class.class.isInstance(keyType), errorMsg); - Assert.isTrue(Class.class.isInstance(valueType), errorMsg); - Assert.isTrue(String.class.equals(keyType), errorMsg); - Assert.isTrue(Object.class.equals(valueType), errorMsg); - } - } - - } -} +/* + * 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.repository.query; + +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +import org.springframework.core.MethodParameter; +import org.springframework.data.repository.query.Param; +import org.springframework.data.repository.query.Parameter; +import org.springframework.data.repository.query.Parameters; +import org.springframework.util.Assert; + +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.annotation.BindVars; + +/** + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner + */ +public class ArangoParameters extends Parameters { + + private final int queryOptionsIndex; + private final int bindVarsIndex; + + public ArangoParameters(final Method method) { + super(method); + assertSingleSpecialParameter(ArangoParameter::isQueryOptions, + "Multiple AqlQueryOptions parameters are not allowed! Offending method: " + method); + assertSingleSpecialParameter(ArangoParameter::isBindVars, + "Multiple @BindVars parameters are not allowed! Offending method: " + method); + assertNonDuplicateParamNames(method); + this.queryOptionsIndex = getIndexOfSpecialParameter(ArangoParameter::isQueryOptions); + this.bindVarsIndex = getIndexOfSpecialParameter(ArangoParameter::isBindVars); + } + + private ArangoParameters(final List parameters, final int queryOptionsIndex, + final int bindVarsIndex) { + super(parameters); + this.queryOptionsIndex = queryOptionsIndex; + this.bindVarsIndex = bindVarsIndex; + } + + @Override + protected ArangoParameter createParameter(final MethodParameter parameter) { + return new ArangoParameter(parameter); + } + + @Override + protected ArangoParameters createFrom(final List parameters) { + return new ArangoParameters(parameters, this.queryOptionsIndex, this.bindVarsIndex); + } + + public boolean hasQueryOptions() { + return this.queryOptionsIndex != -1; + } + + public int getQueryOptionsIndex() { + return this.queryOptionsIndex; + } + + public boolean hasBindVars() { + return this.bindVarsIndex != -1; + } + + public int getBindVarsIndex() { + return this.bindVarsIndex; + } + + private int getIndexOfSpecialParameter(final Predicate condition) { + for (int index = 0; index < getNumberOfParameters(); ++index) { + final ArangoParameter param = getParameter(index); + if (condition.test(param)) { + return index; + } + } + return -1; + } + + private void assertSingleSpecialParameter(final Predicate condition, final String message) { + boolean found = false; + for (int index = 0; index < getNumberOfParameters(); ++index) { + final ArangoParameter param = getParameter(index); + if (condition.test(param)) { + Assert.isTrue(!found, message); + found = true; + } + } + } + + private void assertNonDuplicateParamNames(final Method method) { + final ArangoParameters bindableParams = getBindableParameters(); + final int bindableParamsSize = bindableParams.getNumberOfParameters(); + final Set paramNames = new HashSet<>(bindableParamsSize); + for (int i = 0; i < bindableParamsSize; ++i) { + final ArangoParameter param = bindableParams.getParameter(i); + final String name = param.getName(); + if (name != null) { + Assert.isTrue(!paramNames.contains(name), "Duplicate parameter name! Offending method: " + method); + paramNames.add(name); + } + } + } + + static class ArangoParameter extends Parameter { + + private static final Pattern BIND_PARAM_PATTERN = Pattern.compile("^@?[A-Za-z0-9][A-Za-z0-9_]*$"); + private static final String NAMED_PARAMETER_TEMPLATE = "@%s"; + private static final String POSITION_PARAMETER_TEMPLATE = "@%d"; + + private final MethodParameter parameter; + + public ArangoParameter(final MethodParameter parameter) { + super(parameter); + this.parameter = parameter; + assertCorrectBindParamPattern(); + assertCorrectBindVarsType(); + } + + @Override + public boolean isSpecialParameter() { + return super.isSpecialParameter() || isQueryOptions() || isBindVars(); + } + + public boolean isQueryOptions() { + return AqlQueryOptions.class.isAssignableFrom(parameter.getParameterType()); + } + + public boolean isBindVars() { + return parameter.hasParameterAnnotation(BindVars.class); + } + + @SuppressWarnings("deprecation") + @Override + public String getName() { + final Param annotation = parameter.getParameterAnnotation(Param.class); + // we need to support the old @Param annotation + final com.arangodb.springframework.annotation.Param oldAnnotation = parameter + .getParameterAnnotation(com.arangodb.springframework.annotation.Param.class); + return annotation == null ? (oldAnnotation == null ? null : oldAnnotation.value()) : annotation.value(); + } + + @SuppressWarnings("deprecation") + @Override + public boolean isExplicitlyNamed() { + // we need to support the old @Param annotation + return super.isExplicitlyNamed() + || parameter.hasMethodAnnotation(com.arangodb.springframework.annotation.Param.class); + } + + @Override + public String getPlaceholder() { + if (isNamedParameter()) { + return String.format(NAMED_PARAMETER_TEMPLATE, getName()); + } else { + return String.format(POSITION_PARAMETER_TEMPLATE, getIndex()); + } + } + + private void assertCorrectBindParamPattern() { + if (isExplicitlyNamed()) { + final String name = getName(); + final boolean matches = BIND_PARAM_PATTERN.matcher(name).matches(); + Assert.isTrue(matches, "@Param has invalid format! Offending parameter: parameter " + + parameter.getParameterIndex() + " on method " + parameter.getMethod()); + } + } + + private void assertCorrectBindVarsType() { + final String errorMsg = "@BindVars parameter must be of type Map! Offending parameter: parameter " + + parameter.getParameterIndex() + " on method " + parameter.getMethod(); + + if (isBindVars()) { + Assert.isTrue(Map.class.equals(parameter.getParameterType()), errorMsg); + + final Type type = parameter.getGenericParameterType(); + Assert.isTrue(ParameterizedType.class.isInstance(type), errorMsg); + + final Type[] genericTypes = ((ParameterizedType) type).getActualTypeArguments(); + Assert.isTrue(genericTypes.length == 2, errorMsg); + + final Type keyType = genericTypes[0]; + final Type valueType = genericTypes[1]; + + Assert.isTrue(Class.class.isInstance(keyType), errorMsg); + Assert.isTrue(Class.class.isInstance(valueType), errorMsg); + Assert.isTrue(String.class.equals(keyType), errorMsg); + Assert.isTrue(Object.class.equals(valueType), errorMsg); + } + } + + } +} From 61eeec4107b544472b04971fba1e15f1b9ff2187 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 4 May 2018 10:49:21 +0200 Subject: [PATCH 35/94] Fix distance calculation in derived geo queries --- ChangeLog | 7 ++++ .../repository/query/DerivedArangoQuery.java | 7 ++-- .../core/template/ArangoIndexTest.java | 36 ++++++++++++------- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4216010fd..47edb78b7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +v1.1.2 (2018-xx-xx) +--------------------------- +* deprecated @Param annotation, there is already such an annotation from Spring Data +* fixed floating point numbers in derived queries +* added support for named queries +* fixed distance calculation in derived geo queries + v1.1.1 (2018-04-23) --------------------------- * fixed serialization of enums (issue #39) diff --git a/src/main/java/com/arangodb/springframework/repository/query/DerivedArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/DerivedArangoQuery.java index 040fd21d7..86f19ccd5 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/DerivedArangoQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/DerivedArangoQuery.java @@ -48,7 +48,7 @@ public class DerivedArangoQuery extends AbstractArangoQuery { private final MappingContext, ArangoPersistentProperty> context; private final List geoFields; - public DerivedArangoQuery(ArangoQueryMethod method, ArangoOperations operations) { + public DerivedArangoQuery(final ArangoQueryMethod method, final ArangoOperations operations) { super(method, operations); this.tree = new PartTree(method.getName(), this.domainClass); this.context = operations.getConverter().getMappingContext(); @@ -78,8 +78,9 @@ protected boolean isExistsQuery() { private List getGeoFields() { final List geoFields = new LinkedList<>(); if (method.isGeoQuery()) { - for (IndexEntity index : operations.collection(domainClass).getIndexes()) { - if ((index.getType() == IndexType.geo1)) { + for (final IndexEntity index : operations.collection(domainClass).getIndexes()) { + final IndexType type = index.getType(); + if (type == IndexType.geo || type == IndexType.geo1 || type == IndexType.geo2) { geoFields.addAll(index.getFields()); } } 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 f45086898..325cf2bef 100644 --- a/src/test/java/com/arangodb/springframework/core/template/ArangoIndexTest.java +++ b/src/test/java/com/arangodb/springframework/core/template/ArangoIndexTest.java @@ -58,6 +58,18 @@ @ContextConfiguration(classes = { ArangoTestConfiguration.class }) public class ArangoIndexTest extends AbstractArangoTest { + private IndexType geo1() { + return geoType(IndexType.geo1); + } + + private IndexType geo2() { + return geoType(IndexType.geo2); + } + + private IndexType geoType(final IndexType type) { + return Integer.valueOf(template.getVersion().getVersion().split("\\.")[1]) >= 4 ? IndexType.geo : type; + } + public static class HashIndexedSingleFieldTestEntity { @HashIndexed private String a; @@ -340,9 +352,9 @@ public void singleFieldGeoIndexed() { assertThat(template.collection(GeoIndexedSingleFieldTestEntity.class).getIndexes().size(), is(2)); assertThat(template.collection(GeoIndexedSingleFieldTestEntity.class).getIndexes().stream() .map(i -> i.getType()).collect(Collectors.toList()), - hasItems(IndexType.primary, IndexType.geo1)); + hasItems(IndexType.primary, geo1())); assertThat(template.collection(GeoIndexedSingleFieldTestEntity.class).getIndexes().stream() - .filter(i -> i.getType() == IndexType.geo1).findFirst().get().getFields(), + .filter(i -> i.getType() == geo1()).findFirst().get().getFields(), hasItems("a")); } @@ -358,7 +370,7 @@ public void multipleSingleFieldGeoIndexed() { assertThat(template.collection(GeoIndexedMultipleSingleFieldTestEntity.class).getIndexes().size(), is(3)); assertThat(template.collection(GeoIndexedMultipleSingleFieldTestEntity.class).getIndexes().stream() .map(i -> i.getType()).collect(Collectors.toList()), - hasItems(IndexType.primary, IndexType.geo1)); + hasItems(IndexType.primary, geo1())); } @GeoIndex(fields = { "a" }) @@ -370,9 +382,9 @@ public void singleFieldGeoIndex() { assertThat(template.collection(GeoIndexWithSingleFieldTestEntity.class).getIndexes().size(), is(2)); assertThat(template.collection(GeoIndexWithSingleFieldTestEntity.class).getIndexes().stream() .map(i -> i.getType()).collect(Collectors.toList()), - hasItems(IndexType.primary, IndexType.geo1)); + hasItems(IndexType.primary, geo1())); assertThat(template.collection(GeoIndexedSingleFieldTestEntity.class).getIndexes().stream() - .filter(i -> i.getType() == IndexType.geo1).findFirst().get().getFields(), + .filter(i -> i.getType() == geo1()).findFirst().get().getFields(), hasItems("a")); } @@ -386,7 +398,7 @@ public void multipleSingleFieldGeoIndex() { assertThat(template.collection(GeoIndexedMultipleSingleFieldTestEntity.class).getIndexes().size(), is(3)); assertThat(template.collection(GeoIndexedMultipleSingleFieldTestEntity.class).getIndexes().stream() .map(i -> i.getType()).collect(Collectors.toList()), - hasItems(IndexType.primary, IndexType.geo1)); + hasItems(IndexType.primary, geo1())); } @GeoIndex(fields = { "a", "b" }) @@ -398,9 +410,9 @@ public void multiFieldGeoIndex() { assertThat(template.collection(GeoIndexWithMultiFieldTestEntity.class).getIndexes().size(), is(2)); assertThat(template.collection(GeoIndexWithMultiFieldTestEntity.class).getIndexes().stream() .map(i -> i.getType()).collect(Collectors.toList()), - hasItems(IndexType.primary, IndexType.geo2)); + hasItems(IndexType.primary, geo2())); assertThat(template.collection(GeoIndexWithMultiFieldTestEntity.class).getIndexes().stream() - .filter(i -> i.getType() == IndexType.geo2).findFirst().get().getFields(), + .filter(i -> i.getType() == geo2()).findFirst().get().getFields(), hasItems("a", "b")); } @@ -413,7 +425,7 @@ public void multipleIndexesGeoIndex() { assertThat(template.collection(GeoIndexedMultipleSingleFieldTestEntity.class).getIndexes().size(), is(3)); assertThat(template.collection(GeoIndexedMultipleSingleFieldTestEntity.class).getIndexes().stream() .map(i -> i.getType()).collect(Collectors.toList()), - hasItems(IndexType.primary, IndexType.geo1)); + hasItems(IndexType.primary, geo1())); } public static class FulltextIndexedSingleFieldTestEntity { @@ -506,7 +518,7 @@ public void differentIndexedAnnotationsSameField() { assertThat( template.collection(DifferentIndexedAnnotations.class).getIndexes().stream().map(i -> i.getType()) .collect(Collectors.toList()), - hasItems(IndexType.primary, IndexType.hash, IndexType.skiplist, IndexType.persistent, IndexType.geo1, + hasItems(IndexType.primary, IndexType.hash, IndexType.skiplist, IndexType.persistent, geo1(), IndexType.fulltext)); } @@ -525,7 +537,7 @@ public void differentIndexAnnotations() { assertThat( template.collection(DifferentIndexAnnotations.class).getIndexes().stream().map(i -> i.getType()) .collect(Collectors.toList()), - hasItems(IndexType.primary, IndexType.hash, IndexType.skiplist, IndexType.persistent, IndexType.geo1, + hasItems(IndexType.primary, IndexType.hash, IndexType.skiplist, IndexType.persistent, geo1(), IndexType.fulltext)); } @@ -549,7 +561,7 @@ public void multipleDifferentIndexAnnotations() { assertThat( template.collection(MultipleDifferentIndexAnnotations.class).getIndexes().stream().map(i -> i.getType()) .collect(Collectors.toList()), - hasItems(IndexType.primary, IndexType.hash, IndexType.skiplist, IndexType.persistent, IndexType.geo1, + hasItems(IndexType.primary, IndexType.hash, IndexType.skiplist, IndexType.persistent, geo1(), IndexType.fulltext)); } From 3ecbf95cf34cd46839b7df3c4e6055e1cd623a5e Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 4 May 2018 13:08:03 +0200 Subject: [PATCH 36/94] prepare release 1.1.2 --- ChangeLog | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 47edb78b7..d00b7a514 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,4 @@ -v1.1.2 (2018-xx-xx) +v1.1.2 (2018-05-04) --------------------------- * deprecated @Param annotation, there is already such an annotation from Spring Data * fixed floating point numbers in derived queries diff --git a/pom.xml b/pom.xml index fbedb0768..d882861fe 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.2-SNAPSHOT + 1.1.2 2017 jar From 193b3e42e28be1ba5a49cff90dfdabf00c291a57 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 4 May 2018 13:19:36 +0200 Subject: [PATCH 37/94] prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d882861fe..6531391f1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.2 + 1.1.3-SNAPSHOT 2017 jar From 41921ccc86deb8e96a61d6965ec7af07b2525cfc Mon Sep 17 00:00:00 2001 From: Christian Lechner <6638938+christian-lechner@users.noreply.github.com> Date: Wed, 16 May 2018 14:44:28 +0200 Subject: [PATCH 38/94] fix unused params (#44) --- .../core/template/ArangoTemplate.java | 34 ++++++------------- 1 file changed, 10 insertions(+), 24 deletions(-) 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 19bdb0d9e..d3ec65805 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -32,7 +32,6 @@ import java.util.stream.Collectors; import org.springframework.dao.DataAccessException; -import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; @@ -155,9 +154,9 @@ private ArangoCollection _collection(final Class entityClass) { } private ArangoCollection _collection(final Class entityClass, final String id) { - final String name = determineCollectionFromId(Optional.ofNullable(id)) - .orElse(getPersistentEntity(entityClass).getCollection()); - final ArangoPersistentEntity persistentEntity = getPersistentEntity(entityClass); + final ArangoPersistentEntity persistentEntity = converter.getMappingContext() + .getRequiredPersistentEntity(entityClass); + final String name = determineCollectionFromId(Optional.ofNullable(id)).orElse(persistentEntity.getCollection()); return _collection(name, persistentEntity, persistentEntity.getCollectionOptions()); } @@ -266,16 +265,6 @@ private static void ensureFulltextIndex( collection.ensureFulltextIndex(Collections.singleton(value.getFieldName()), options); } - private ArangoPersistentEntity getPersistentEntity(final Class entityClass) { - final ArangoPersistentEntity persistentEntity = converter.getMappingContext() - .getPersistentEntity(entityClass); - if (persistentEntity == null) { - new InvalidDataAccessApiUsageException( - "No persistent entity information found for the type " + entityClass.getName()); - } - return persistentEntity; - } - private Optional determineCollectionFromId(final Optional id) { return id.map(i -> { final String[] split = i.split("/"); @@ -385,7 +374,7 @@ public MultiDocumentEntity update( try { final MultiDocumentEntity res = _collection(entityClass) .updateDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); - updateDBFields(values, entityClass, res); + updateDBFields(values, res); return res; } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); @@ -404,7 +393,7 @@ public DocumentEntity update(final String id, final Object value, final Document throws DataAccessException { try { final DocumentEntity res = _collection(value.getClass(), id).updateDocument(determineDocumentKeyFromId(id), - toDBEntity(value)); + toDBEntity(value), options); updateDBFields(value, res); return res; } catch (final ArangoDBException e) { @@ -425,7 +414,7 @@ public MultiDocumentEntity replace( try { final MultiDocumentEntity res = _collection(entityClass) .replaceDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); - updateDBFields(values, entityClass, res); + updateDBFields(values, res); return res; } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); @@ -505,7 +494,7 @@ public MultiDocumentEntity insert( try { final MultiDocumentEntity res = _collection(entityClass) .insertDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); - updateDBFields(values, entityClass, res); + updateDBFields(values, res); return res; } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); @@ -522,7 +511,7 @@ public MultiDocumentEntity insert( @Override public DocumentEntity insert(final Object value, final DocumentCreateOptions options) throws DataAccessException { try { - final DocumentEntity res = _collection(value.getClass()).insertDocument(toDBEntity(value)); + final DocumentEntity res = _collection(value.getClass()).insertDocument(toDBEntity(value), options); updateDBFields(value, res); return res; } catch (final ArangoDBException e) { @@ -539,7 +528,7 @@ public DocumentEntity insert(final Object value) throws DataAccessException { public DocumentEntity insert(final String collectionName, final Object value, final DocumentCreateOptions options) throws DataAccessException { try { - final DocumentEntity res = _collection(collectionName).insertDocument(toDBEntity(value)); + final DocumentEntity res = _collection(collectionName).insertDocument(toDBEntity(value), options); updateDBFields(value, res); return res; } catch (final ArangoDBException e) { @@ -618,10 +607,7 @@ public void upsert(final Iterable value, final UpsertStrategy strategy) t } } - private void updateDBFields( - final Iterable values, - final Class entityClass, - final MultiDocumentEntity res) { + private void updateDBFields(final Iterable values, final MultiDocumentEntity res) { final Iterator valueIterator = values.iterator(); if (res.getErrors().isEmpty()) { final Iterator documentIterator = res.getDocuments().iterator(); From 30c4c5ad5986c77513b56d57486737fa22043cfa Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 17 May 2018 09:34:26 +0200 Subject: [PATCH 39/94] Fix Arango Converters add @ReadingConverter * @WritingConverter --- .../convert/JodaTimeStringConverters.java | 278 ++++++++-------- .../core/convert/TimeStringConverters.java | 310 +++++++++--------- 2 files changed, 305 insertions(+), 283 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/convert/JodaTimeStringConverters.java b/src/main/java/com/arangodb/springframework/core/convert/JodaTimeStringConverters.java index 899b16b72..32bad0704 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/JodaTimeStringConverters.java +++ b/src/main/java/com/arangodb/springframework/core/convert/JodaTimeStringConverters.java @@ -1,134 +1,144 @@ -/* - * 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.core.convert; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.joda.time.DateTime; -import org.joda.time.Instant; -import org.joda.time.LocalDate; -import org.joda.time.LocalDateTime; -import org.springframework.core.convert.converter.Converter; -import org.springframework.util.ClassUtils; - -import com.arangodb.velocypack.module.joda.internal.util.JodaTimeUtil; - -/** - * @author Mark Vollmary - * - */ -public class JodaTimeStringConverters { - - private static final boolean JODA_TIME_IS_PRESENT = ClassUtils.isPresent("org.joda.time.LocalDate", null); - - public static Collection> getConvertersToRegister() { - if (!JODA_TIME_IS_PRESENT) { - return Collections.emptySet(); - } - final List> converters = new ArrayList<>(); - converters.add(InstantToStringConverter.INSTANCE); - converters.add(DateTimeToStringConverter.INSTANCE); - converters.add(LocalDateToStringConverter.INSTANCE); - converters.add(LocalDateTimeToStringConverter.INSTANCE); - - converters.add(StringToInstantConverter.INSTANCE); - converters.add(StringToDateTimeConverter.INSTANCE); - converters.add(StringToLocalDateConverter.INSTANCE); - converters.add(StringToLocalDateTimeConverter.INSTANCE); - return converters; - } - - public static enum InstantToStringConverter implements Converter { - INSTANCE; - - @Override - public String convert(final Instant source) { - return source == null ? null : JodaTimeUtil.format(source); - } - } - - public static enum DateTimeToStringConverter implements Converter { - INSTANCE; - - @Override - public String convert(final DateTime source) { - return source == null ? null : JodaTimeUtil.format(source); - } - } - - public static enum LocalDateToStringConverter implements Converter { - INSTANCE; - - @Override - public String convert(final LocalDate source) { - return source == null ? null : JodaTimeUtil.format(source); - } - } - - public static enum LocalDateTimeToStringConverter implements Converter { - INSTANCE; - - @Override - public String convert(final LocalDateTime source) { - return source == null ? null : JodaTimeUtil.format(source); - } - } - - public static enum StringToInstantConverter implements Converter { - INSTANCE; - - @Override - public Instant convert(final String source) { - return source == null ? null : JodaTimeUtil.parseInstant(source); - } - } - - public static enum StringToDateTimeConverter implements Converter { - INSTANCE; - - @Override - public DateTime convert(final String source) { - return source == null ? null : JodaTimeUtil.parseDateTime(source); - } - } - - public static enum StringToLocalDateConverter implements Converter { - INSTANCE; - - @Override - public LocalDate convert(final String source) { - return source == null ? null : JodaTimeUtil.parseLocalDate(source); - } - } - - public static enum StringToLocalDateTimeConverter implements Converter { - INSTANCE; - - @Override - public LocalDateTime convert(final String source) { - return source == null ? null : JodaTimeUtil.parseLocalDateTime(source); - } - } - -} +/* + * 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.core.convert; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.joda.time.DateTime; +import org.joda.time.Instant; +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.convert.WritingConverter; +import org.springframework.util.ClassUtils; + +import com.arangodb.velocypack.module.joda.internal.util.JodaTimeUtil; + +/** + * @author Mark Vollmary + * + */ +public class JodaTimeStringConverters { + + private static final boolean JODA_TIME_IS_PRESENT = ClassUtils.isPresent("org.joda.time.LocalDate", null); + + public static Collection> getConvertersToRegister() { + if (!JODA_TIME_IS_PRESENT) { + return Collections.emptySet(); + } + final List> converters = new ArrayList<>(); + converters.add(InstantToStringConverter.INSTANCE); + converters.add(DateTimeToStringConverter.INSTANCE); + converters.add(LocalDateToStringConverter.INSTANCE); + converters.add(LocalDateTimeToStringConverter.INSTANCE); + + converters.add(StringToInstantConverter.INSTANCE); + converters.add(StringToDateTimeConverter.INSTANCE); + converters.add(StringToLocalDateConverter.INSTANCE); + converters.add(StringToLocalDateTimeConverter.INSTANCE); + return converters; + } + + @WritingConverter + public static enum InstantToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(final Instant source) { + return source == null ? null : JodaTimeUtil.format(source); + } + } + + @WritingConverter + public static enum DateTimeToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(final DateTime source) { + return source == null ? null : JodaTimeUtil.format(source); + } + } + + @WritingConverter + public static enum LocalDateToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(final LocalDate source) { + return source == null ? null : JodaTimeUtil.format(source); + } + } + + @WritingConverter + public static enum LocalDateTimeToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(final LocalDateTime source) { + return source == null ? null : JodaTimeUtil.format(source); + } + } + + @ReadingConverter + public static enum StringToInstantConverter implements Converter { + INSTANCE; + + @Override + public Instant convert(final String source) { + return source == null ? null : JodaTimeUtil.parseInstant(source); + } + } + + @ReadingConverter + public static enum StringToDateTimeConverter implements Converter { + INSTANCE; + + @Override + public DateTime convert(final String source) { + return source == null ? null : JodaTimeUtil.parseDateTime(source); + } + } + + @ReadingConverter + public static enum StringToLocalDateConverter implements Converter { + INSTANCE; + + @Override + public LocalDate convert(final String source) { + return source == null ? null : JodaTimeUtil.parseLocalDate(source); + } + } + + @ReadingConverter + public static enum StringToLocalDateTimeConverter implements Converter { + INSTANCE; + + @Override + public LocalDateTime convert(final String source) { + return source == null ? null : JodaTimeUtil.parseLocalDateTime(source); + } + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java b/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java index 17cb852e6..61bc01687 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java +++ b/src/main/java/com/arangodb/springframework/core/convert/TimeStringConverters.java @@ -1,149 +1,161 @@ -/* - * 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.core.convert; - -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.springframework.core.convert.converter.Converter; - -import com.arangodb.velocypack.module.jdk8.internal.util.JavaTimeUtil; - -/** - * @author Mark Vollmary - * @author Christian Lechner - * - */ -public class TimeStringConverters { - - public static Collection> getConvertersToRegister() { - final List> converters = new ArrayList<>(); - converters.add(InstantToStringConverter.INSTANCE); - converters.add(LocalDateToStringConverter.INSTANCE); - converters.add(LocalDateTimeToStringConverter.INSTANCE); - converters.add(OffsetDateTimeToStringConverter.INSTANCE); - converters.add(ZonedDateTimeToStringConverter.INSTANCE); - - converters.add(StringToInstantConverter.INSTANCE); - converters.add(StringToLocalDateConverter.INSTANCE); - converters.add(StringToLocalDateTimeConverter.INSTANCE); - converters.add(StringToOffsetDateTimeConverter.INSTANCE); - converters.add(StringToZonedDateTimeConverter.INSTANCE); - return converters; - } - - public static enum InstantToStringConverter implements Converter { - INSTANCE; - - @Override - public String convert(final Instant source) { - return source == null ? null : JavaTimeUtil.format(source); - } - } - - public static enum LocalDateToStringConverter implements Converter { - INSTANCE; - - @Override - public String convert(final LocalDate source) { - return source == null ? null : JavaTimeUtil.format(source); - } - } - - public static enum LocalDateTimeToStringConverter implements Converter { - INSTANCE; - - @Override - public String convert(final LocalDateTime source) { - return source == null ? null : JavaTimeUtil.format(source); - } - } - - public static enum OffsetDateTimeToStringConverter implements Converter { - INSTANCE; - - @Override - public String convert(final OffsetDateTime source) { - return source == null ? null : JavaTimeUtil.format(source); - } - } - - public static enum ZonedDateTimeToStringConverter implements Converter { - INSTANCE; - - @Override - public String convert(final ZonedDateTime source) { - return source == null ? null : JavaTimeUtil.format(source); - } - } - - public static enum StringToInstantConverter implements Converter { - INSTANCE; - - @Override - public Instant convert(final String source) { - return source == null ? null : JavaTimeUtil.parseInstant(source); - } - } - - public static enum StringToLocalDateConverter implements Converter { - INSTANCE; - - @Override - public LocalDate convert(final String source) { - return source == null ? null : JavaTimeUtil.parseLocalDate(source); - } - } - - public static enum StringToLocalDateTimeConverter implements Converter { - INSTANCE; - - @Override - public LocalDateTime convert(final String source) { - return source == null ? null : JavaTimeUtil.parseLocalDateTime(source); - } - } - - public static enum StringToOffsetDateTimeConverter implements Converter { - INSTANCE; - - @Override - public OffsetDateTime convert(final String source) { - return source == null ? null : JavaTimeUtil.parseOffsetDateTime(source); - } - } - - public static enum StringToZonedDateTimeConverter implements Converter { - INSTANCE; - - @Override - public ZonedDateTime convert(final String source) { - return source == null ? null : JavaTimeUtil.parseZonedDateTime(source); - } - } - -} +/* + * 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.core.convert; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.data.convert.ReadingConverter; +import org.springframework.data.convert.WritingConverter; + +import com.arangodb.velocypack.module.jdk8.internal.util.JavaTimeUtil; + +/** + * @author Mark Vollmary + * @author Christian Lechner + * + */ +public class TimeStringConverters { + + public static Collection> getConvertersToRegister() { + final List> converters = new ArrayList<>(); + converters.add(InstantToStringConverter.INSTANCE); + converters.add(LocalDateToStringConverter.INSTANCE); + converters.add(LocalDateTimeToStringConverter.INSTANCE); + converters.add(OffsetDateTimeToStringConverter.INSTANCE); + converters.add(ZonedDateTimeToStringConverter.INSTANCE); + + converters.add(StringToInstantConverter.INSTANCE); + converters.add(StringToLocalDateConverter.INSTANCE); + converters.add(StringToLocalDateTimeConverter.INSTANCE); + converters.add(StringToOffsetDateTimeConverter.INSTANCE); + converters.add(StringToZonedDateTimeConverter.INSTANCE); + return converters; + } + + @WritingConverter + public static enum InstantToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(final Instant source) { + return source == null ? null : JavaTimeUtil.format(source); + } + } + + @WritingConverter + public static enum LocalDateToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(final LocalDate source) { + return source == null ? null : JavaTimeUtil.format(source); + } + } + + @WritingConverter + public static enum LocalDateTimeToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(final LocalDateTime source) { + return source == null ? null : JavaTimeUtil.format(source); + } + } + + @WritingConverter + public static enum OffsetDateTimeToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(final OffsetDateTime source) { + return source == null ? null : JavaTimeUtil.format(source); + } + } + + @WritingConverter + public static enum ZonedDateTimeToStringConverter implements Converter { + INSTANCE; + + @Override + public String convert(final ZonedDateTime source) { + return source == null ? null : JavaTimeUtil.format(source); + } + } + + @ReadingConverter + public static enum StringToInstantConverter implements Converter { + INSTANCE; + + @Override + public Instant convert(final String source) { + return source == null ? null : JavaTimeUtil.parseInstant(source); + } + } + + @ReadingConverter + public static enum StringToLocalDateConverter implements Converter { + INSTANCE; + + @Override + public LocalDate convert(final String source) { + return source == null ? null : JavaTimeUtil.parseLocalDate(source); + } + } + + @ReadingConverter + public static enum StringToLocalDateTimeConverter implements Converter { + INSTANCE; + + @Override + public LocalDateTime convert(final String source) { + return source == null ? null : JavaTimeUtil.parseLocalDateTime(source); + } + } + + @ReadingConverter + public static enum StringToOffsetDateTimeConverter implements Converter { + INSTANCE; + + @Override + public OffsetDateTime convert(final String source) { + return source == null ? null : JavaTimeUtil.parseOffsetDateTime(source); + } + } + + @ReadingConverter + public static enum StringToZonedDateTimeConverter implements Converter { + INSTANCE; + + @Override + public ZonedDateTime convert(final String source) { + return source == null ? null : JavaTimeUtil.parseZonedDateTime(source); + } + } + +} From d85467599f3b8eadeac286a9c1d9a41bac225dd3 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 17 May 2018 13:13:24 +0200 Subject: [PATCH 40/94] Fix support for ArangoCusor as query-method return type (#46) compatibility with Spring Data 2.0.7-RELEASE --- ChangeLog | 58 +-- .../repository/query/AbstractArangoQuery.java | 349 +++++++++--------- .../repository/query/ArangoQueryMethod.java | 10 +- 3 files changed, 214 insertions(+), 203 deletions(-) diff --git a/ChangeLog b/ChangeLog index d00b7a514..4b3d5148d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,27 +1,31 @@ -v1.1.2 (2018-05-04) ---------------------------- -* deprecated @Param annotation, there is already such an annotation from Spring Data -* fixed floating point numbers in derived queries -* added support for named queries -* fixed distance calculation in derived geo queries - -v1.1.1 (2018-04-23) ---------------------------- -* fixed serialization of enums (issue #39) - -v1.1.0 (2018-04-20) ---------------------------- -* added DataIntegrityViolationException to ExceptionTranslator -* fixed race conditions in ArangoTemplate when creating database and collections (issue #35) -* added type mapper implementation & custom conversions extension (issue #33) -* fixed missing deserialization of return types of @Query methods(issue #21) -* fixed handling of java.time in converters (issue #36, #24, #25) -* fixed handling of org.joda.time in converters (issue #36) - -v2.0.3 (2018-03-23) ---------------------------- -* fixed missing WITH information in derived query - -v1.0.1 (2018-01-26) ---------------------------- -* fixed missing WITH information in AQL when resolving annotation @Relations (Issue #8) +v1.1.3 (2018-06-04) +--------------------------- +* fixed support for ArangoCusor as query-method return type + +v1.1.2 (2018-05-04) +--------------------------- +* deprecated @Param annotation, there is already such an annotation from Spring Data +* fixed floating point numbers in derived queries +* added support for named queries +* fixed distance calculation in derived geo queries + +v1.1.1 (2018-04-23) +--------------------------- +* fixed serialization of enums (issue #39) + +v1.1.0 (2018-04-20) +--------------------------- +* added DataIntegrityViolationException to ExceptionTranslator +* fixed race conditions in ArangoTemplate when creating database and collections (issue #35) +* added type mapper implementation & custom conversions extension (issue #33) +* fixed missing deserialization of return types of @Query methods(issue #21) +* fixed handling of java.time in converters (issue #36, #24, #25) +* fixed handling of org.joda.time in converters (issue #36) + +v2.0.3 (2018-03-23) +--------------------------- +* fixed missing WITH information in derived query + +v1.0.1 (2018-01-26) +--------------------------- +* fixed missing WITH information in AQL when resolving annotation @Relations (Issue #8) diff --git a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java index 1585769b0..1585dcbd4 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java @@ -1,173 +1,176 @@ -/* - * 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.query; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.data.geo.GeoPage; -import org.springframework.data.repository.query.RepositoryQuery; -import org.springframework.util.Assert; - -import com.arangodb.ArangoCursor; -import com.arangodb.model.AqlQueryOptions; -import com.arangodb.springframework.core.ArangoOperations; - -/** - * - * @author Audrius Malele - * @author Mark McCormick - * @author Mark Vollmary - * @author Christian Lechner - */ -public abstract class AbstractArangoQuery implements RepositoryQuery { - - protected final ArangoQueryMethod method; - protected final ArangoOperations operations; - protected final Class domainClass; - - public AbstractArangoQuery(ArangoQueryMethod method, ArangoOperations operations) { - Assert.notNull(method, "ArangoQueryMethod must not be null!"); - Assert.notNull(operations, "ArangoOperations must not be null!"); - this.method = method; - this.operations = operations; - this.domainClass = method.getEntityInformation().getJavaType(); - } - - @Override - public Object execute(Object[] parameters) { - final ArangoParameterAccessor accessor = new ArangoParametersParameterAccessor(method, parameters); - final Map bindVars = new HashMap<>(); - - AqlQueryOptions options = mergeQueryOptions(method.getAnnotatedQueryOptions(), accessor.getQueryOptions()); - if (options == null) { - options = new AqlQueryOptions(); - } - - if (method.isPageQuery() || GeoPage.class.equals(method.getReturnType())) { - options.fullCount(true); - } - - final String query = createQuery(accessor, bindVars, options); - final ArangoCursor result = operations.query(query, bindVars, options, getResultClass()); - return convertResult(result, accessor); - } - - @Override - public ArangoQueryMethod getQueryMethod() { - return method; - } - - /** - * Implementations should create an AQL query with the given - * {@link com.arangodb.springframework.repository.query.ArangoParameterAccessor} and set necessary binding - * parameters and query options. - * - * @param accessor - * provides access to the actual arguments - * @param bindVars - * the binding parameter map - * @param options - * contains the merged {@link com.arangodb.model.AqlQueryOptions} - * @return the created AQL query - */ - protected abstract String createQuery( - ArangoParameterAccessor accessor, - Map bindVars, - AqlQueryOptions options); - - protected abstract boolean isCountQuery(); - - protected abstract boolean isExistsQuery(); - - /** - * Merges AqlQueryOptions derived from @QueryOptions with dynamically passed AqlQueryOptions which takes priority - * - * @param oldStatic - * @param newDynamic - * @return - */ - protected AqlQueryOptions mergeQueryOptions(final AqlQueryOptions oldStatic, final AqlQueryOptions newDynamic) { - if (oldStatic == null) { - return newDynamic; - } - if (newDynamic == null) { - return oldStatic; - } - final Integer batchSize = newDynamic.getBatchSize(); - if (batchSize != null) { - oldStatic.batchSize(batchSize); - } - final Integer maxPlans = newDynamic.getMaxPlans(); - if (maxPlans != null) { - oldStatic.maxPlans(maxPlans); - } - final Integer ttl = newDynamic.getTtl(); - if (ttl != null) { - oldStatic.ttl(ttl); - } - final Boolean cache = newDynamic.getCache(); - if (cache != null) { - oldStatic.cache(cache); - } - final Boolean count = newDynamic.getCount(); - if (count != null) { - oldStatic.count(count); - } - final Boolean fullCount = newDynamic.getFullCount(); - if (fullCount != null) { - oldStatic.fullCount(fullCount); - } - final Boolean profile = newDynamic.getProfile(); - if (profile != null) { - oldStatic.profile(profile); - } - final Collection rules = newDynamic.getRules(); - if (rules != null) { - oldStatic.rules(rules); - } - return oldStatic; - } - - private Class getResultClass() { - if (isExistsQuery()) { - return Integer.class; - } - if (method.isGeoQuery()) { - return Object.class; - } - return method.getReturnedObjectType(); - } - - private Object convertResult(final ArangoCursor result, ArangoParameterAccessor accessor) { - if (isExistsQuery()) { - if (!result.hasNext()) { - return false; - } - return Integer.valueOf(result.next().toString()) > 0; - } - final ArangoResultConverter resultConverter = new ArangoResultConverter(accessor, result, operations, - domainClass); - return resultConverter.convertResult(method.getReturnType()); - } - -} +/* + * 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.query; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.data.geo.GeoPage; +import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.util.Assert; + +import com.arangodb.ArangoCursor; +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.core.ArangoOperations; + +/** + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner + */ +public abstract class AbstractArangoQuery implements RepositoryQuery { + + protected final ArangoQueryMethod method; + protected final ArangoOperations operations; + protected final Class domainClass; + + public AbstractArangoQuery(final ArangoQueryMethod method, final ArangoOperations operations) { + Assert.notNull(method, "ArangoQueryMethod must not be null!"); + Assert.notNull(operations, "ArangoOperations must not be null!"); + this.method = method; + this.operations = operations; + this.domainClass = method.getEntityInformation().getJavaType(); + } + + @Override + public Object execute(final Object[] parameters) { + final ArangoParameterAccessor accessor = new ArangoParametersParameterAccessor(method, parameters); + final Map bindVars = new HashMap<>(); + + AqlQueryOptions options = mergeQueryOptions(method.getAnnotatedQueryOptions(), accessor.getQueryOptions()); + if (options == null) { + options = new AqlQueryOptions(); + } + + if (method.isPageQuery() || GeoPage.class.equals(method.getReturnType())) { + options.fullCount(true); + } + + final String query = createQuery(accessor, bindVars, options); + final ArangoCursor result = operations.query(query, bindVars, options, getResultClass()); + return convertResult(result, accessor); + } + + @Override + public ArangoQueryMethod getQueryMethod() { + return method; + } + + /** + * Implementations should create an AQL query with the given + * {@link com.arangodb.springframework.repository.query.ArangoParameterAccessor} and set necessary binding + * parameters and query options. + * + * @param accessor + * provides access to the actual arguments + * @param bindVars + * the binding parameter map + * @param options + * contains the merged {@link com.arangodb.model.AqlQueryOptions} + * @return the created AQL query + */ + protected abstract String createQuery( + ArangoParameterAccessor accessor, + Map bindVars, + AqlQueryOptions options); + + protected abstract boolean isCountQuery(); + + protected abstract boolean isExistsQuery(); + + /** + * Merges AqlQueryOptions derived from @QueryOptions with dynamically passed AqlQueryOptions which takes priority + * + * @param oldStatic + * @param newDynamic + * @return + */ + protected AqlQueryOptions mergeQueryOptions(final AqlQueryOptions oldStatic, final AqlQueryOptions newDynamic) { + if (oldStatic == null) { + return newDynamic; + } + if (newDynamic == null) { + return oldStatic; + } + final Integer batchSize = newDynamic.getBatchSize(); + if (batchSize != null) { + oldStatic.batchSize(batchSize); + } + final Integer maxPlans = newDynamic.getMaxPlans(); + if (maxPlans != null) { + oldStatic.maxPlans(maxPlans); + } + final Integer ttl = newDynamic.getTtl(); + if (ttl != null) { + oldStatic.ttl(ttl); + } + final Boolean cache = newDynamic.getCache(); + if (cache != null) { + oldStatic.cache(cache); + } + final Boolean count = newDynamic.getCount(); + if (count != null) { + oldStatic.count(count); + } + final Boolean fullCount = newDynamic.getFullCount(); + if (fullCount != null) { + oldStatic.fullCount(fullCount); + } + final Boolean profile = newDynamic.getProfile(); + if (profile != null) { + oldStatic.profile(profile); + } + final Collection rules = newDynamic.getRules(); + if (rules != null) { + oldStatic.rules(rules); + } + return oldStatic; + } + + private Class getResultClass() { + if (isExistsQuery()) { + return Integer.class; + } + if (method.isGeoQuery()) { + return Object.class; + } + if (ArangoCursor.class.isAssignableFrom(method.getReturnType().getType())) { + return method.getReturnType().getRequiredComponentType().getType(); + } + return method.getReturnedObjectType(); + } + + private Object convertResult(final ArangoCursor result, final ArangoParameterAccessor accessor) { + if (isExistsQuery()) { + if (!result.hasNext()) { + return false; + } + return Integer.valueOf(result.next().toString()) > 0; + } + final ArangoResultConverter resultConverter = new ArangoResultConverter(accessor, result, operations, + domainClass); + return resultConverter.convertResult(method.getReturnType().getType()); + } + +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java index 4d8b95fcb..992c4219f 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java @@ -32,6 +32,8 @@ import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.QueryMethod; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; import org.springframework.util.StringUtils; import com.arangodb.model.AqlQueryOptions; @@ -50,10 +52,12 @@ public class ArangoQueryMethod extends QueryMethod { private static final List> GEO_TYPES = Arrays.asList(GeoResult.class, GeoResults.class, GeoPage.class); private final Method method; + private final TypeInformation returnType; public ArangoQueryMethod(final Method method, final RepositoryMetadata metadata, final ProjectionFactory factory) { super(method, metadata, factory); this.method = method; + this.returnType = ClassTypeInformation.from(metadata.getRepositoryInterface()).getReturnType(method); } @Override @@ -117,9 +121,9 @@ public AqlQueryOptions getAnnotatedQueryOptions() { public QueryOptions getQueryOptionsAnnotation() { return AnnotatedElementUtils.findMergedAnnotation(method, QueryOptions.class); } - - public Class getReturnType() { - return method.getReturnType(); + + public TypeInformation getReturnType() { + return returnType; } public boolean isGeoQuery() { From 772fa06f646c077b48f2be7552ff290b4bccb51a Mon Sep 17 00:00:00 2001 From: Christian Lechner <6638938+christian-lechner@users.noreply.github.com> Date: Tue, 22 May 2018 13:41:38 +0200 Subject: [PATCH 41/94] ArangoCursor support in repos (for Spring Data Commons 2.0.7) & projection support (#49) * support ArangoCursor through RepositoryMetadata * make use of the result processor --- .../repository/ArangoRepositoryFactory.java | 56 +++++++++++++++++++ .../repository/query/AbstractArangoQuery.java | 23 +++++--- .../repository/CustomerRepository.java | 24 +++++++- .../repository/query/ArangoAqlQueryTest.java | 38 +++++++++++++ .../testdata/CustomerNameProjection.java | 7 +++ 5 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 src/test/java/com/arangodb/springframework/testdata/CustomerNameProjection.java diff --git a/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java b/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java index df259e411..2781dc509 100644 --- a/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java +++ b/src/main/java/com/arangodb/springframework/repository/ArangoRepositoryFactory.java @@ -25,15 +25,22 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.AnnotationRepositoryMetadata; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.Assert; +import com.arangodb.ArangoCursor; import com.arangodb.springframework.core.ArangoOperations; import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; @@ -76,6 +83,15 @@ protected Class getRepositoryBaseClass(final RepositoryMetadata metadata) { return SimpleArangoRepository.class; } + @Override + protected RepositoryMetadata getRepositoryMetadata(Class repositoryInterface) { + Assert.notNull(repositoryInterface, "Repository interface must not be null!"); + + return Repository.class.isAssignableFrom(repositoryInterface) + ? new DefaultArangoRepositoryMetadata(repositoryInterface) + : new AnnotationArangoRepositoryMetadata(repositoryInterface); + } + @Override protected QueryLookupStrategy getQueryLookupStrategy(final QueryLookupStrategy.Key key, final EvaluationContextProvider evaluationContextProvider) { @@ -120,4 +136,44 @@ public RepositoryQuery resolveQuery(final Method method, final RepositoryMetadat } + static class DefaultArangoRepositoryMetadata extends DefaultRepositoryMetadata { + + private final TypeInformation typeInformation; + + public DefaultArangoRepositoryMetadata(Class repositoryInterface) { + super(repositoryInterface); + typeInformation = ClassTypeInformation.from(repositoryInterface); + } + + @Override + public Class getReturnedDomainClass(Method method) { + if (ArangoCursor.class.isAssignableFrom(method.getReturnType())) { + return typeInformation.getReturnType(method).getComponentType().getType(); + } else { + return super.getReturnedDomainClass(method); + } + } + + } + + static class AnnotationArangoRepositoryMetadata extends AnnotationRepositoryMetadata { + + private final TypeInformation typeInformation; + + public AnnotationArangoRepositoryMetadata(Class repositoryInterface) { + super(repositoryInterface); + typeInformation = ClassTypeInformation.from(repositoryInterface); + } + + @Override + public Class getReturnedDomainClass(Method method) { + if (ArangoCursor.class.isAssignableFrom(method.getReturnType())) { + return typeInformation.getReturnType(method).getComponentType().getType(); + } else { + return super.getReturnedDomainClass(method); + } + } + + } + } diff --git a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java index 1585dcbd4..b8b48e298 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java @@ -26,6 +26,7 @@ import org.springframework.data.geo.GeoPage; import org.springframework.data.repository.query.RepositoryQuery; +import org.springframework.data.repository.query.ResultProcessor; import org.springframework.util.Assert; import com.arangodb.ArangoCursor; @@ -68,8 +69,12 @@ public Object execute(final Object[] parameters) { } final String query = createQuery(accessor, bindVars, options); - final ArangoCursor result = operations.query(query, bindVars, options, getResultClass()); - return convertResult(result, accessor); + + final ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor); + final Class typeToRead = getTypeToRead(processor); + + final ArangoCursor result = operations.query(query, bindVars, options, typeToRead); + return processor.processResult(convertResult(result, accessor)); } @Override @@ -148,17 +153,17 @@ protected AqlQueryOptions mergeQueryOptions(final AqlQueryOptions oldStatic, fin return oldStatic; } - private Class getResultClass() { + private Class getTypeToRead(final ResultProcessor processor) { if (isExistsQuery()) { return Integer.class; } + if (method.isGeoQuery()) { - return Object.class; + return Map.class; } - if (ArangoCursor.class.isAssignableFrom(method.getReturnType().getType())) { - return method.getReturnType().getRequiredComponentType().getType(); - } - return method.getReturnedObjectType(); + + final Class typeToRead = processor.getReturnedType().getTypeToRead(); + return typeToRead != null ? typeToRead : Map.class; } private Object convertResult(final ArangoCursor result, final ArangoParameterAccessor accessor) { @@ -166,7 +171,7 @@ private Object convertResult(final ArangoCursor result, final ArangoParameter if (!result.hasNext()) { return false; } - return Integer.valueOf(result.next().toString()) > 0; + return (int) result.next() > 0; } final ArangoResultConverter resultConverter = new ArangoResultConverter(accessor, result, operations, domainClass); diff --git a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java index 2d3c9987f..738de790c 100644 --- a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java +++ b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java @@ -28,6 +28,7 @@ import com.arangodb.springframework.annotation.QueryOptions; import com.arangodb.springframework.repository.query.derived.geo.Ring; import com.arangodb.springframework.testdata.Customer; +import com.arangodb.springframework.testdata.CustomerNameProjection; /** * @@ -188,10 +189,10 @@ List findByNestedCustomersNestedCustomerShoppingCartProductsLocationWi List getByOwnsContainsName(String name); // Count query - + @Query("RETURN COUNT(@@collection)") long queryCount(@Param("@collection") Class collection); - + // Date query @Query("RETURN DATE_ISO8601(1474988621)") @@ -200,4 +201,21 @@ List findByNestedCustomersNestedCustomerShoppingCartProductsLocationWi // Named query Customer findOneByIdNamedQuery(@Param("id") String id); -} \ No newline at end of file + + // Static projection + + @Query("FOR c IN customer FILTER c._id == @id RETURN c") + CustomerNameProjection findOneByIdWithStaticProjection(@Param("id") String id); + + @Query("FOR c IN customer FILTER c.age >= 18 RETURN c") + List findManyLegalAgeWithStaticProjection(); + + // Dynamic projection + + @Query("FOR c IN customer FILTER c._id == @id RETURN c") + T findOneByIdWithDynamicProjection(@Param("id") String id, Class projection); + + @Query("FOR c IN customer FILTER c.age >= 18 RETURN c") + List findManyLegalAgeWithDynamicProjection(Class projection); + +} diff --git a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java index bc97914a9..fc0f9cb8f 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java @@ -1,5 +1,7 @@ package com.arangodb.springframework.repository.query; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsIn.isOneOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -20,6 +22,7 @@ import com.arangodb.springframework.core.convert.DBDocumentEntity; import com.arangodb.springframework.repository.AbstractArangoRepositoryTest; import com.arangodb.springframework.testdata.Customer; +import com.arangodb.springframework.testdata.CustomerNameProjection; /** * @@ -153,4 +156,39 @@ public void findOneByIdNamedQueryTest() { final Customer retrieved = repository.findOneByIdNamedQuery(john.getId()); assertEquals(john, retrieved); } + + @Test + public void findOneByIdWithStaticProjectionTest() { + repository.save(customers); + final CustomerNameProjection retrieved = repository.findOneByIdWithStaticProjection(john.getId()); + assertEquals(retrieved.getName(), john.getName()); + } + + @Test + public void findManyLegalAgeWithStaticProjectionTest() { + repository.save(customers); + final List retrieved = repository.findManyLegalAgeWithStaticProjection(); + for (CustomerNameProjection proj : retrieved) { + assertThat(proj.getName(), isOneOf(john.getName(), bob.getName())); + } + } + + @Test + public void findOneByIdWithDynamicProjectionTest() { + repository.save(customers); + final CustomerNameProjection retrieved = repository.findOneByIdWithDynamicProjection(john.getId(), + CustomerNameProjection.class); + assertEquals(retrieved.getName(), john.getName()); + } + + @Test + public void findManyLegalAgeWithDynamicProjectionTest() { + repository.save(customers); + final List retrieved = repository + .findManyLegalAgeWithDynamicProjection(CustomerNameProjection.class); + for (CustomerNameProjection proj : retrieved) { + assertThat(proj.getName(), isOneOf(john.getName(), bob.getName())); + } + } + } diff --git a/src/test/java/com/arangodb/springframework/testdata/CustomerNameProjection.java b/src/test/java/com/arangodb/springframework/testdata/CustomerNameProjection.java new file mode 100644 index 000000000..425e98f99 --- /dev/null +++ b/src/test/java/com/arangodb/springframework/testdata/CustomerNameProjection.java @@ -0,0 +1,7 @@ +package com.arangodb.springframework.testdata; + +public interface CustomerNameProjection { + + String getName(); + +} From 9af6504ae134942a4f15c966dfe3f09af67c94b6 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 24 May 2018 17:39:13 +0200 Subject: [PATCH 42/94] Fix compile error --- .../repository/query/AbstractArangoQuery.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java index b8b48e298..ede2cc681 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java @@ -69,10 +69,10 @@ public Object execute(final Object[] parameters) { } final String query = createQuery(accessor, bindVars, options); - + final ResultProcessor processor = method.getResultProcessor().withDynamicProjection(accessor); final Class typeToRead = getTypeToRead(processor); - + final ArangoCursor result = operations.query(query, bindVars, options, typeToRead); return processor.processResult(convertResult(result, accessor)); } @@ -157,11 +157,11 @@ private Class getTypeToRead(final ResultProcessor processor) { if (isExistsQuery()) { return Integer.class; } - + if (method.isGeoQuery()) { return Map.class; } - + final Class typeToRead = processor.getReturnedType().getTypeToRead(); return typeToRead != null ? typeToRead : Map.class; } @@ -171,7 +171,7 @@ private Object convertResult(final ArangoCursor result, final ArangoParameter if (!result.hasNext()) { return false; } - return (int) result.next() > 0; + return (int) (Integer) result.next() > 0; } final ArangoResultConverter resultConverter = new ArangoResultConverter(accessor, result, operations, domainClass); From 14d814dbbead85cf91a5e24fd9e29ef97c11e70d Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 28 May 2018 09:41:41 +0200 Subject: [PATCH 43/94] Fix casting --- .../springframework/repository/query/AbstractArangoQuery.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java index ede2cc681..a6134f4da 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java @@ -171,7 +171,7 @@ private Object convertResult(final ArangoCursor result, final ArangoParameter if (!result.hasNext()) { return false; } - return (int) (Integer) result.next() > 0; + return (Integer) result.next() > 0; } final ArangoResultConverter resultConverter = new ArangoResultConverter(accessor, result, operations, domainClass); From bcee0fa1e3f04e53857cb9f8c09dbb9aaa3acac5 Mon Sep 17 00:00:00 2001 From: Christian Lechner <6638938+christian-lechner@users.noreply.github.com> Date: Mon, 4 Jun 2018 12:55:08 +0200 Subject: [PATCH 44/94] implement paging and sorting for native queries (#51) --- docs/Drivers/SpringData/Reference/README.md | 59 +++++- .../springframework/core/util/AqlUtils.java | 200 ++++++++++++++++++ .../repository/SimpleArangoRepository.java | 198 ++++++++--------- .../query/StringBasedArangoQuery.java | 79 +++++-- .../query/derived/DerivedQueryCreator.java | 23 +- .../core/util/AqlUtilsTest.java | 196 +++++++++++++++++ .../repository/CustomerRepository.java | 6 + .../repository/query/ArangoAqlQueryTest.java | 38 ++++ 8 files changed, 659 insertions(+), 140 deletions(-) create mode 100644 src/main/java/com/arangodb/springframework/core/util/AqlUtils.java create mode 100644 src/test/java/com/arangodb/springframework/core/util/AqlUtilsTest.java diff --git a/docs/Drivers/SpringData/Reference/README.md b/docs/Drivers/SpringData/Reference/README.md index 675cbb97c..9b5ebc280 100644 --- a/docs/Drivers/SpringData/Reference/README.md +++ b/docs/Drivers/SpringData/Reference/README.md @@ -272,7 +272,7 @@ ArangoCursor cursor = myRepo.query(bindVars); You can set additional options for the query and the created cursor over the class `AqlQueryOptions` which you can simply define as a method parameter without a specific name. AqlQuery options can also be defined with the `@QueryOptions` annotation, as shown below. AqlQueryOptions from an annotation and those from an argument are merged if both exist, with those in the argument taking precedence. -The `AqlQueryOptions` allows you to set the cursor time-to-life, batch-size, caching flag and several other settings. This special parameter works with both query-methods and finder-methods. Keep in mind that some options, like time-to-life, are only effective if the method return type is`ArangoCursor` or `Iterable`. +The `AqlQueryOptions` allows you to set the cursor time-to-live, batch-size, caching flag and several other settings. This special parameter works with both query-methods and finder-methods. Keep in mind that some options, like time-to-live, are only effective if the method return type is`ArangoCursor` or `Iterable`. ```java public interface MyRepository extends Repository { @@ -296,6 +296,63 @@ public interface MyRepository extends Repository { } ``` +### Paging and sorting + +Spring Data ArangoDB supports Spring Data's `Pageable` and `Sort` parameters for repository query methods. If these parameters are used together with a native query, either through `@Query` annotation or named queries, a placeholder must be specified: + +* `#pageable` for `Pageable` parameter +* `#sort` for `Sort` parameter + +Sort properties or paths are attributes separated by dots (e.g. `customer.age`). Some rules apply for them: + +* they must not begin or end with a dot (e.g. `.customer.age`) +* dots in attributes are supported, but the whole attribute must be enclosed by backticks (e.g. `` customer.`attr.with.dots` `` +* backticks in attributes are supported, but they must be escaped with a backslash (e.g. `` customer.attr_with\` ``) +* any backslashes (that do not escape a backtick) are escaped (e.g. `customer\` => `customer\\`) + +``` +just.`some`.`attributes.that`.`form\``.a path\`.\ is converted to +`just`.`some`.`attributes.that`.`form\``.`a path\``.`\\` +``` + +Native queries example: + +```java +public interface CustomerRepository extends ArangoRepository { + + @Query("FOR c IN customer FILTER c.name == @1 #pageable RETURN c") + Page findByNameNative(Pageable pageable, String name); + + @Query("FOR c IN customer FILTER c.name == @1 #sort RETURN c") + List findByNameNative(Sort sort, String name); +} + +// don't forget to specify the var name of the document +final Pageable page = PageRequest.of(1, 10, Sort.by("c.age")); +repository.findByNameNative(page, "Matt"); + +final Sort sort = Sort.by(Direction.DESC, "c.age"); +repository.findByNameNative(sort, "Tony"); +``` + +Derived queries example: + +```java +public interface CustomerRepository extends ArangoRepository { + + Page findByName(Pageable pageable, String name); + + List findByName(Sort sort, String name); +} + +// no var name is necessary for derived queries +final Pageable page = PageRequest.of(1, 10, Sort.by("age")); +repository.findByName(page, "Matt"); + +final Sort sort = Sort.by(Direction.DESC, "age"); +repository.findByName(sort, "Tony"); +``` + # Mapping ## Introduction diff --git a/src/main/java/com/arangodb/springframework/core/util/AqlUtils.java b/src/main/java/com/arangodb/springframework/core/util/AqlUtils.java new file mode 100644 index 000000000..80fcd384a --- /dev/null +++ b/src/main/java/com/arangodb/springframework/core/util/AqlUtils.java @@ -0,0 +1,200 @@ +/* + * 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.util; + +import java.util.StringJoiner; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + +/** + * + * @author Christian Lechner + */ +public final class AqlUtils { + + private AqlUtils() { + + } + + public static String buildLimitClause(final Pageable pageable) { + if (pageable.isUnpaged()) { + return ""; + } + + final StringJoiner clause = new StringJoiner(", ", "LIMIT ", ""); + clause.add(String.valueOf(pageable.getOffset())); + clause.add(String.valueOf(pageable.getPageSize())); + return clause.toString(); + } + + public static String buildPageableClause(final Pageable pageable) { + return buildPageableClause(pageable, null); + } + + public static String buildPageableClause(final Pageable pageable, @Nullable final String varName) { + return buildPageableClause(pageable, varName, new StringBuilder()).toString(); + } + + private static StringBuilder buildPageableClause( + final Pageable pageable, + @Nullable final String varName, + final StringBuilder clause) { + + if (pageable.isUnpaged()) { + return clause; + } + + Sort sort = pageable.getSort(); + buildSortClause(sort, varName, clause); + + if (sort.isSorted()) { + clause.append(' '); + } + + clause.append("LIMIT ").append(pageable.getOffset()).append(", ").append(pageable.getPageSize()); + return clause; + } + + public static String buildSortClause(final Sort sort) { + return buildSortClause(sort, null); + } + + public static String buildSortClause(final Sort sort, @Nullable final String varName) { + return buildSortClause(sort, varName, new StringBuilder()).toString(); + } + + private static StringBuilder buildSortClause( + final Sort sort, + @Nullable final String varName, + final StringBuilder clause) { + + if (sort.isUnsorted()) { + return clause; + } + + final String prefix = StringUtils.hasText(varName) ? escapeSortProperty(varName) : null; + clause.append("SORT "); + boolean first = true; + + for (final Sort.Order order : sort) { + if (!first) { + clause.append(", "); + } else { + first = false; + } + + if (prefix != null) { + clause.append(prefix).append('.'); + } + final String escapedProperty = escapeSortProperty(order.getProperty()); + clause.append(escapedProperty).append(' ').append(order.getDirection()); + } + return clause; + + } + + private static String escapeSortProperty(final String str) { + // dots are not allowed at start/end + if (str.charAt(0) == '.' || str.charAt(str.length() - 1) == '.') { + throw new IllegalArgumentException("Sort properties must not begin or end with a dot!"); + } + + final StringBuilder escaped = new StringBuilder(); + escaped.append('`'); + + // keep track if we are inside an escaped sequence + boolean inEscapedSeq = false; + + for (int i = 0; i < str.length(); ++i) { + final char currChar = str.charAt(i); + final boolean hasNext = (i + 1) < str.length(); + final char nextChar = hasNext ? str.charAt(i + 1) : '\0'; + + if (currChar == '\\') { + // keep escaped backticks + if (nextChar == '`') { + escaped.append("\\`"); + ++i; + } + // escape backslashes + else { + escaped.append("\\\\"); + } + } + + // current char is an unescaped backtick + else if (currChar == '`') { + inEscapedSeq = !inEscapedSeq; + + final boolean isStartOrEnd = i == 0 || !hasNext; + final boolean isNextCharDotOutsideEscapedSeq = nextChar == '.' && !inEscapedSeq; + + // unescaped backticks are only allowed at start/end of attributes + if (!isStartOrEnd && !isNextCharDotOutsideEscapedSeq) { + throw new IllegalArgumentException( + "Sort properties must only contain backticks at beginning/end of attributes or when escaped."); + } + } + + else if (currChar == '.') { + // the dot is part of an attribute name when inside escaped sequence + if (inEscapedSeq) { + // add dot without escaping + escaped.append('.'); + } + + else { + // properties can only contain 2+ dots in escaped sequences + if (nextChar == '.') { + throw new IllegalArgumentException( + "Sort properties may not contain 2+ consecutive dots when outside a backtick escape sequence!"); + } + // consume optional backtick + else if (nextChar == '`') { + inEscapedSeq = !inEscapedSeq; + ++i; + } + + // close previous escape sequence and open new one + escaped.append("`.`"); + } + } + + // keep others + else { + escaped.append(currChar); + } + } + + // check for an open escape sequence + if (inEscapedSeq) { + throw new IllegalArgumentException( + "A sort property contains an unclosed backtick escape sequence! The cause may be a missing backtick."); + } + + escaped.append('`'); + return escaped.toString(); + } + +} diff --git a/src/main/java/com/arangodb/springframework/repository/SimpleArangoRepository.java b/src/main/java/com/arangodb/springframework/repository/SimpleArangoRepository.java index 809a0d467..651c3eccd 100644 --- a/src/main/java/com/arangodb/springframework/repository/SimpleArangoRepository.java +++ b/src/main/java/com/arangodb/springframework/repository/SimpleArangoRepository.java @@ -39,11 +39,12 @@ import com.arangodb.springframework.core.ArangoOperations; import com.arangodb.springframework.core.ArangoOperations.UpsertStrategy; import com.arangodb.springframework.core.mapping.ArangoMappingContext; -import com.arangodb.springframework.repository.query.derived.DerivedQueryCreator; +import com.arangodb.springframework.core.util.AqlUtils; /** - * The implementation of all CRUD, paging and sorting functionality in ArangoRepository from the Spring Data Commons - * CRUD repository and PagingAndSorting repository + * The implementation of all CRUD, paging and sorting functionality in + * ArangoRepository from the Spring Data Commons CRUD repository and + * PagingAndSorting repository */ @Repository @SuppressWarnings({ "rawtypes", "unchecked" }) @@ -57,10 +58,9 @@ public class SimpleArangoRepository implements ArangoRepository { /** * - * @param arangoOperations - * The template used to execute much of the functionality of this class - * @param domainClass - * the class type of this repository + * @param arangoOperations The template used to execute much of the + * functionality of this class + * @param domainClass the class type of this repository */ public SimpleArangoRepository(final ArangoOperations arangoOperations, final Class domainClass) { super(); @@ -73,8 +73,7 @@ public SimpleArangoRepository(final ArangoOperations arangoOperations, final Cla /** * Saves the passed entity to the database using upsert from the template * - * @param entity - * the entity to be saved to the database + * @param entity the entity to be saved to the database * @return the updated entity with any id/key/rev saved */ // TODO refactor once template.upsert() is implemented @@ -87,9 +86,9 @@ public S save(final S entity) { /** * Saves the given iterable of entities to the database * - * @param entities - * the iterable of entities to be saved to the database - * @return the iterable of updated entities with any id/key/rev saved in each entity + * @param entities the iterable of entities to be saved to the database + * @return the iterable of updated entities with any id/key/rev saved in each + * entity */ // TODO refactor once template.upsert() is implemented @Override @@ -101,8 +100,7 @@ public Iterable save(final Iterable entities) { /** * Finds if a document with the given id exists in the database * - * @param id - * the id of the document to search for + * @param id the id of the document to search for * @return the object representing the document if found */ @Override @@ -113,8 +111,7 @@ public T findOne(final String id) { /** * Checks if a document exists or not based on the given id or key * - * @param s - * represents either the key or id of a document to check for + * @param s represents either the key or id of a document to check for * @return returns true if the document is found, false otherwise */ @Override @@ -135,9 +132,9 @@ public Iterable findAll() { /** * Finds all documents with the an id or key in the argument * - * @param strings - * an iterable with ids/keys of documents to get - * @return an iterable with documents in the collection which have a id/key in the argument + * @param strings an iterable with ids/keys of documents to get + * @return an iterable with documents in the collection which have a id/key in + * the argument */ @Override public Iterable findAll(final Iterable strings) { @@ -145,7 +142,8 @@ public Iterable findAll(final Iterable strings) { } /** - * Counts the number of documents in the collection for the type of this repository + * Counts the number of documents in the collection for the type of this + * repository * * @return long with number of documents */ @@ -157,8 +155,7 @@ public long count() { /** * Deletes the document with the given id or key * - * @param s - * id or key of document to be deleted + * @param s id or key of document to be deleted */ @Override public void delete(final String s) { @@ -166,17 +163,17 @@ public void delete(final String s) { } /** - * Deletes document in the database representing the given object, by getting it's id + * Deletes document in the database representing the given object, by getting + * it's id * - * @param entity - * the entity to be deleted from the database + * @param entity the entity to be deleted from the database */ @Override public void delete(final T entity) { String id = null; try { - id = (String) arangoOperations.getConverter().getMappingContext().getPersistentEntity(domainClass) - .getIdProperty().getField().get(entity); + id = (String) arangoOperations.getConverter().getMappingContext().getPersistentEntity(domainClass).getIdProperty() + .getField().get(entity); } catch (final IllegalAccessException e) { e.printStackTrace(); } @@ -186,8 +183,7 @@ public void delete(final T entity) { /** * Deletes all the given documents from the database * - * @param entities - * iterable of entities to be deleted from the database + * @param entities iterable of entities to be deleted from the database */ // TODO refactor if this can be done through one database call @Override @@ -204,28 +200,27 @@ public void deleteAll() { } /** - * Gets all documents in the collection for the class type of this repository, with the given sort applied + * Gets all documents in the collection for the class type of this repository, + * with the given sort applied * - * @param sort - * the sort object to use for sorting + * @param sort the sort object to use for sorting * @return an iterable with all the documents in the collection */ @Override public Iterable findAll(final Sort sort) { - final String sortString = DerivedQueryCreator.buildSortString(sort); return new Iterable() { @Override public Iterator iterator() { - return execute("", sortString, "", new HashMap<>()); + return findAllInternal(sort, null, new HashMap<>()); } }; } /** - * Gets all documents in the collection for the class type of this repository, with pagination + * Gets all documents in the collection for the class type of this repository, + * with pagination * - * @param pageable - * the pageable object to use for pagination of the results + * @param pageable the pageable object to use for pagination of the results * @return an iterable with all the documents in the collection */ @Override @@ -233,16 +228,15 @@ public Page findAll(final Pageable pageable) { if (pageable == null) { LOGGER.debug("Pageable in findAll(Pageable) is null"); } - final String sort = DerivedQueryCreator.buildSortString(pageable.getSort()); - final String limit = String.format(" LIMIT %d, %d", pageable.getOffset(), pageable.getPageSize()); - final ArangoCursor result = execute("", sort, limit, new HashMap<>()); + + final ArangoCursor result = findAllInternal(pageable, null, new HashMap<>()); final List content = result.asListRemaining(); return new PageImpl<>(content, pageable, result.getStats().getFullCount()); } /** * Gets the name of the collection for this repository - * + * * @return the name of the collection */ private String getCollectionName() { @@ -252,85 +246,65 @@ private String getCollectionName() { /** * Finds one document which matches the given example object * - * @param example - * example object to construct query with - * @param + * @param example example object to construct query with + * @param * @return An object representing the example if it exists, else null */ @Override public S findOne(final Example example) { - final Map bindVars = new HashMap<>(); - final String predicate = exampleConverter.convertExampleToPredicate(example, bindVars); - final String filter = predicate.length() == 0 ? "" : " FILTER " + predicate; - final ArangoCursor cursor = execute(filter, "", "", bindVars); + final ArangoCursor cursor = findAllInternal((Pageable) null, example, new HashMap()); return cursor.hasNext() ? (S) cursor.next() : null; } /** * Finds all documents which match with the given example * - * @param example - * example object to construct query with - * @param + * @param example example object to construct query with + * @param * @return iterable of all matching documents */ @Override public Iterable findAll(final Example example) { - final Map bindVars = new HashMap<>(); - final String predicate = exampleConverter.convertExampleToPredicate(example, bindVars); - final String filter = predicate.length() == 0 ? "" : " FILTER " + predicate; - final ArangoCursor cursor = execute(filter, "", "", bindVars); + final ArangoCursor cursor = findAllInternal((Pageable) null, example, new HashMap<>()); return cursor; } /** - * Finds all documents which match with the given example, then apply the given sort to results + * Finds all documents which match with the given example, then apply the given + * sort to results * - * @param example - * example object to construct query with - * @param sort - * sort object to sort results - * @param + * @param example example object to construct query with + * @param sort sort object to sort results + * @param * @return sorted iterable of all matching documents */ @Override public Iterable findAll(final Example example, final Sort sort) { - final Map bindVars = new HashMap<>(); - final String predicate = exampleConverter.convertExampleToPredicate(example, bindVars); - final String filter = predicate.length() == 0 ? "" : " FILTER " + predicate; - final String sortString = DerivedQueryCreator.buildSortString(sort); - final ArangoCursor cursor = execute(filter, sortString, "", bindVars); + final ArangoCursor cursor = findAllInternal(sort, example, new HashMap()); return cursor; } /** * Finds all documents which match with the given example, with pagination * - * @param example - * example object to construct query with - * @param pageable - * pageable object to apply pagination with - * @param + * @param example example object to construct query with + * @param pageable pageable object to apply pagination with + * @param * @return iterable of all matching documents, with pagination */ @Override public Page findAll(final Example example, final Pageable pageable) { - final Map bindVars = new HashMap<>(); - final String predicate = exampleConverter.convertExampleToPredicate(example, bindVars); - final String filter = predicate.length() == 0 ? "" : " FILTER " + predicate; - final String sortString = DerivedQueryCreator.buildSortString(pageable.getSort()); - final String limit = String.format(" LIMIT %d, %d", pageable.getOffset(), pageable.getPageSize()); - final ArangoCursor cursor = execute(filter, sortString, limit, bindVars); + final ArangoCursor cursor = findAllInternal(pageable, example, new HashMap()); final List content = cursor.asListRemaining(); return new PageImpl<>((List) content, pageable, cursor.getStats().getFullCount()); } /** - * Counts the number of documents in the collection which match with the given example - * - * @param example - * example object to construct query with - * @param + * Counts the number of documents in the collection which match with the given + * example + * + * @param example example object to construct query with + * @param * @return number of matching documents found */ @Override @@ -339,16 +313,16 @@ public long count(final Example example) { final String predicate = exampleConverter.convertExampleToPredicate(example, bindVars); final String filter = predicate.length() == 0 ? "" : " FILTER " + predicate; final String query = String.format("FOR e IN %s%s COLLECT WITH COUNT INTO length RETURN length", - getCollectionName(), filter); + getCollectionName(), filter); final ArangoCursor cursor = arangoOperations.query(query, bindVars, null, Long.class); return cursor.next(); } /** * Checks if any documents match with the given example - * + * * @param example - * @param + * @param * @return true if any matches are found, else false */ @Override @@ -356,26 +330,38 @@ public boolean exists(final Example example) { return count(example) > 0; } - /** - * Execute a query to the database - * - * @param filter - * filter statement to be put in query - * @param sort - * any sort to be applied - * @param limit - * a limit if one exists - * @param bindVars - * bindVars for the query being executed - * @return ArangoCursor with the results of the executed query - */ - private ArangoCursor execute( - final String filter, - final String sort, - final String limit, - final Map bindVars) { - final String query = String.format("FOR e IN %s%s%s%s RETURN e", getCollectionName(), filter, sort, limit); - return arangoOperations.query(query, bindVars, - limit.length() == 0 ? null : new AqlQueryOptions().fullCount(true), domainClass); + private ArangoCursor findAllInternal(final Sort sort, final Example example, + final Map bindVars) { + + final String query = String.format("FOR e IN %s %s %s RETURN e", getCollectionName(), + buildFilterClause(example, bindVars), buildSortClause(sort, "e")); + return arangoOperations.query(query, bindVars, null, domainClass); + } + + private ArangoCursor findAllInternal(final Pageable pageable, final Example example, + final Map bindVars) { + + final String query = String.format("FOR e IN %s %s %s RETURN e", getCollectionName(), + buildFilterClause(example, bindVars), buildPageableClause(pageable, "e")); + + return arangoOperations.query(query, bindVars, pageable != null ? new AqlQueryOptions().fullCount(true) : null, + domainClass); + } + + private String buildFilterClause(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); } } diff --git a/src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java index 3abca36af..1aca16ece 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/StringBasedArangoQuery.java @@ -30,6 +30,7 @@ import com.arangodb.model.AqlQueryOptions; import com.arangodb.springframework.core.ArangoOperations; +import com.arangodb.springframework.core.util.AqlUtils; import com.arangodb.springframework.repository.query.ArangoParameters.ArangoParameter; /** @@ -41,8 +42,16 @@ */ public class StringBasedArangoQuery extends AbstractArangoQuery { + private static final String PAGEABLE_PLACEHOLDER = "#pageable"; + + private static final String SORT_PLACEHOLDER = "#sort"; + private static final Pattern BIND_PARAM_PATTERN = Pattern.compile("@(@?[A-Za-z0-9][A-Za-z0-9_]*)"); + private static final Pattern PAGEABLE_PLACEHOLDER_PATTERN = Pattern.compile(Pattern.quote(PAGEABLE_PLACEHOLDER)); + + private static final Pattern SORT_PLACEHOLDER_PATTERN = Pattern.compile(Pattern.quote(SORT_PLACEHOLDER)); + private final String query; private final Set queryBindParams; @@ -55,6 +64,10 @@ public StringBasedArangoQuery(String query, ArangoQueryMethod method, ArangoOper Assert.notNull(query, "Query must not be null!"); this.query = query; + + assertSinglePageablePlaceholder(); + assertSingleSortPlaceholder(); + this.queryBindParams = getBindParamsInQuery(); } @@ -62,6 +75,36 @@ public StringBasedArangoQuery(String query, ArangoQueryMethod method, ArangoOper protected String createQuery(final ArangoParameterAccessor accessor, final Map bindVars, final AqlQueryOptions options) { + extractBindVars(accessor, bindVars); + + return prepareQuery(accessor); + } + + @Override + protected boolean isCountQuery() { + return false; + } + + @Override + protected boolean isExistsQuery() { + return false; + } + + private String prepareQuery(final ArangoParameterAccessor accessor) { + if (accessor.getParameters().hasPageableParameter()) { + final String pageableClause = AqlUtils.buildPageableClause(accessor.getPageable()); + return PAGEABLE_PLACEHOLDER_PATTERN.matcher(query).replaceFirst(pageableClause); + } + + if (accessor.getParameters().hasSortParameter()) { + final String sortClause = AqlUtils.buildSortClause(accessor.getSort()); + return SORT_PLACEHOLDER_PATTERN.matcher(query).replaceFirst(sortClause); + } + + return query; + } + + private void extractBindVars(final ArangoParameterAccessor accessor, final Map bindVars) { final Map bindVarsInParams = accessor.getBindVars(); if (bindVarsInParams != null) { bindVars.putAll(bindVarsInParams); @@ -85,18 +128,6 @@ protected String createQuery(final ArangoParameterAccessor accessor, final Map getBindParamsInQuery() { @@ -138,4 +169,28 @@ private String removeAqlStringLiterals(final String query) { return fixedQuery.toString(); } + private void assertSinglePageablePlaceholder() { + if (method.getParameters().hasPageableParameter()) { + int firstOccurrence = query.indexOf(PAGEABLE_PLACEHOLDER); + int secondOccurrence = query.indexOf(PAGEABLE_PLACEHOLDER, firstOccurrence + PAGEABLE_PLACEHOLDER.length()); + + Assert.isTrue(firstOccurrence > -1 && secondOccurrence < 0, + String.format( + "Native query with Pageable param must contain exactly one pageable placeholder (%s)! Offending method: %s", + PAGEABLE_PLACEHOLDER, method)); + } + } + + private void assertSingleSortPlaceholder() { + if (method.getParameters().hasSortParameter()) { + int firstOccurrence = query.indexOf(SORT_PLACEHOLDER); + int secondOccurrence = query.indexOf(SORT_PLACEHOLDER, firstOccurrence + SORT_PLACEHOLDER.length()); + + Assert.isTrue(firstOccurrence > -1 && secondOccurrence < 0, + String.format( + "Native query with Sort param must contain exactly one sort placeholder (%s)! Offending method: %s", + SORT_PLACEHOLDER, method)); + } + } + } diff --git a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java index 0a3660124..173a3cebd 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java +++ b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java @@ -53,6 +53,7 @@ import com.arangodb.springframework.annotation.Relations; import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; +import com.arangodb.springframework.core.util.AqlUtils; import com.arangodb.springframework.repository.query.ArangoParameterAccessor; import com.arangodb.springframework.repository.query.derived.geo.Ring; @@ -176,7 +177,7 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { final String type = tree.isDelete() ? (" REMOVE e IN " + collectionName) : ((tree.isCountProjection() || tree.isExistsProjection()) ? " RETURN length" : format(" RETURN %s", distanceAdjusted)); - String sortString = buildSortString(sort); + String sortString = " " + AqlUtils.buildSortClause(sort, "e"); if ((!this.geoFields.isEmpty() || isUnique != null && isUnique) && !tree.isDelete() && !tree.isCountProjection() && !tree.isExistsProjection()) { final String distanceSortKey = format(" SORT distance(%s, %f, %f)", geoFields, getUniquePoint()[0], @@ -194,26 +195,6 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { return format(queryTemplate, with, array, predicate, count, sortString, limit, pageable, type); } - /** - * Builds a String representing SORT statement from a given Sort object - * - * @param sort - * @return - */ - public static String buildSortString(final Sort sort) { - if (sort == null) { - LOGGER.debug("Sort in findAll(Sort) is null"); - } - final StringBuilder sortBuilder = new StringBuilder(sort == null ? "" : " SORT"); - if (sort != null) { - for (final Sort.Order order : sort) { - sortBuilder - .append((sortBuilder.length() == 5 ? " " : ", ") + "e." + order.getProperty() + " " + order.getDirection()); - } - } - return sortBuilder.toString(); - } - /** * Escapes special characters which could be used in an operand of LIKE operator * diff --git a/src/test/java/com/arangodb/springframework/core/util/AqlUtilsTest.java b/src/test/java/com/arangodb/springframework/core/util/AqlUtilsTest.java new file mode 100644 index 000000000..aaf606f8f --- /dev/null +++ b/src/test/java/com/arangodb/springframework/core/util/AqlUtilsTest.java @@ -0,0 +1,196 @@ +/* + * 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.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; + +/** + * + * @author Christian Lechner + */ +public class AqlUtilsTest { + + @Test + public void buildLimitClauseTest() { + assertThat(AqlUtils.buildLimitClause(Pageable.unpaged()), is("")); + assertThat(AqlUtils.buildLimitClause(PageRequest.of(0, 1)), is("LIMIT 0, 1")); + assertThat(AqlUtils.buildLimitClause(PageRequest.of(10, 20)), is("LIMIT 200, 20")); + } + + @Test + public void buildPageableClauseTest() { + // Special cases + assertThat(AqlUtils.buildPageableClause(Pageable.unpaged()), is("")); + + // Paging without sort + assertThat(AqlUtils.buildPageableClause(PageRequest.of(0, 1)), is("LIMIT 0, 1")); + assertThat(AqlUtils.buildPageableClause(PageRequest.of(5, 10)), is("LIMIT 50, 10")); + + // Paging with sort + assertThat(AqlUtils.buildPageableClause(PageRequest.of(2, 10, Direction.ASC, "property")), + is("SORT `property` ASC LIMIT 20, 10")); + assertThat(AqlUtils.buildPageableClause(PageRequest.of(2, 10, Direction.ASC, "property"), "var"), + is("SORT `var`.`property` ASC LIMIT 20, 10")); + + assertThat(AqlUtils.buildPageableClause(PageRequest.of(2, 10, Direction.DESC, "property", "property2")), + is("SORT `property` DESC, `property2` DESC LIMIT 20, 10")); + assertThat(AqlUtils.buildPageableClause(PageRequest.of(2, 10, Direction.DESC, "property", "property2"), "var"), + is("SORT `var`.`property` DESC, `var`.`property2` DESC LIMIT 20, 10")); + + assertThat( + AqlUtils.buildPageableClause( + PageRequest.of(2, 10, Sort.by("ascProp").and(Sort.by(Direction.DESC, "descProp")))), + is("SORT `ascProp` ASC, `descProp` DESC LIMIT 20, 10")); + assertThat( + AqlUtils.buildPageableClause( + PageRequest.of(2, 10, Sort.by("ascProp").and(Sort.by(Direction.DESC, "descProp"))), "var"), + is("SORT `var`.`ascProp` ASC, `var`.`descProp` DESC LIMIT 20, 10")); + } + + @Test + public void buildSortClauseTest() { + // Special cases + assertThat(AqlUtils.buildSortClause(Sort.unsorted()), is("")); + + // Others + assertThat(AqlUtils.buildSortClause(Sort.by("property")), is("SORT `property` ASC")); + assertThat(AqlUtils.buildSortClause(Sort.by("property"), "var"), is("SORT `var`.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property")), is("SORT `property` DESC")); + assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property"), "var"), + is("SORT `var`.`property` DESC")); + + assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property", "property2")), + is("SORT `property` DESC, `property2` DESC")); + assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property", "property2"), "var"), + is("SORT `var`.`property` DESC, `var`.`property2` DESC")); + + assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property").and(Sort.by("property2"))), + is("SORT `property` DESC, `property2` ASC")); + assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property").and(Sort.by("property2")), "var"), + is("SORT `var`.`property` DESC, `var`.`property2` ASC")); + } + + @Test + public void sortClauseEscapingTest() { + assertThat(AqlUtils.buildSortClause(Sort.by("property")), is("SORT `property` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by("`property`")), is("SORT `property` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by("`pro\\`perty\\``")), is("SORT `pro\\`perty\\`` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by("`dont.split.property`")), is("SORT `dont.split.property` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by("property.`property`")), is("SORT `property`.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by("property.`.`.`property`")), + is("SORT `property`.`.`.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by("property.\\.property")), + is("SORT `property`.`\\\\`.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by("property.\\\\`.property")), + is("SORT `property`.`\\\\\\``.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by("`property.\\`.property`")), + is("SORT `property.\\`.property` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by("`property.\\``.property")), + is("SORT `property.\\``.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by("`property..property`")), is("SORT `property..property` ASC")); + + assertThat(AqlUtils.buildSortClause(Sort.by("property\\. REMOVE doc IN collection //")), + is("SORT `property\\\\`.` REMOVE doc IN collection //` ASC")); + + // Illegal sort properties + + try { + AqlUtils.buildSortClause(Sort.by(".property")); + Assert.fail(); + } catch (IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(Sort.by("property.")); + Assert.fail(); + } catch (IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(Sort.by("property..property")); + Assert.fail(); + } catch (IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(Sort.by("property.`property")); + Assert.fail(); + } catch (IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(Sort.by("pro`perty.property")); + Assert.fail(); + } catch (IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(Sort.by("`property``.property")); + Assert.fail(); + } catch (IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(Sort.by("`property```.property")); + Assert.fail(); + } catch (IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(Sort.by("`property.`\\`.property`")); + Assert.fail(); + } catch (IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(Sort.by("`property.`\\``.property`")); + Assert.fail(); + } catch (IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(Sort.by("`property`.\\``.property`")); + Assert.fail(); + } catch (IllegalArgumentException e) { + } + + } + +} diff --git a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java index 738de790c..1617a3f22 100644 --- a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java +++ b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java @@ -150,10 +150,16 @@ List findByNameOrLocationWithinOrNameAndSurnameOrNameAndLocationNearAn Customer[] findByNameOrderBySurnameAsc(Sort sort, String name); + @Query("FOR c IN customer FILTER c.name == @1 #sort RETURN c") + List findByNameWithSort(Sort sort, String name); + // PAGEABLE Page readByNameAndSurname(Pageable pageable, String name, AqlQueryOptions options, String surname); + @Query("FOR c IN customer FILTER c.name == @1 AND c.surname == @2 #pageable RETURN c") + Page findByNameAndSurnameWithPageable(Pageable pageable, String name, String surname); + // GEO_RESULT, GEO_RESULTS, GEO_PAGE GeoResult queryByLocationWithin(Point location, double distance); diff --git a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java index fc0f9cb8f..aaad150b8 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java @@ -2,6 +2,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsIn.isOneOf; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -13,6 +14,11 @@ import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import com.arangodb.ArangoCursor; @@ -191,4 +197,36 @@ public void findManyLegalAgeWithDynamicProjectionTest() { } } + @Test + public void pageableTest() { + final List toBeRetrieved = new LinkedList<>(); + repository.save(new Customer("A", "A", 0)); + repository.save(new Customer("A", "A", 1)); + toBeRetrieved.add(new Customer("A", "A", 2)); + repository.save(new Customer("B", "B", 3)); + toBeRetrieved.add(new Customer("A", "A", 4)); + repository.save(new Customer("A", "A", 5)); + repository.saveAll(toBeRetrieved); + final Pageable pageable = PageRequest.of(1, 2, Sort.by("c.age")); + final Page retrieved = repository.findByNameAndSurnameWithPageable(pageable, "A", "A"); + assertThat(retrieved.getTotalElements(), is(5L)); + assertThat(retrieved.getTotalPages(), is(3)); + assertThat(retrieved.getContent(), is(toBeRetrieved)); + } + + @Test + public void sortTest() { + final List toBeRetrieved = new LinkedList<>(); + toBeRetrieved.add(new Customer("A", "B", 2)); + toBeRetrieved.add(new Customer("A", "B", 3)); + toBeRetrieved.add(new Customer("A", "A", 1)); + repository.save(toBeRetrieved.get(1)); + repository.save(toBeRetrieved.get(0)); + repository.save(toBeRetrieved.get(2)); + repository.save(new Customer("C", "C", 0)); + final List retrieved = repository + .findByNameWithSort(Sort.by(Direction.DESC, "c.surname").and(Sort.by("c.age")), "A"); + assertThat(retrieved, is(toBeRetrieved)); + } + } From 57b219325a6bb67fffd698ed30167f78270056c1 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 4 Jun 2018 14:01:07 +0200 Subject: [PATCH 45/94] Fix several errors from merging --- ChangeLog | 1 + .../core/template/ArangoTemplate.java | 1410 ++++++++--------- .../springframework/core/util/AqlUtils.java | 396 +++-- .../query/derived/DerivedQueryCreator.java | 124 +- .../core/util/AqlUtilsTest.java | 391 +++-- .../repository/query/ArangoAqlQueryTest.java | 28 +- 6 files changed, 1176 insertions(+), 1174 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4b3d5148d..f27fdd3d5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ v1.1.3 (2018-06-04) --------------------------- * fixed support for ArangoCusor as query-method return type +* added paging and sorting support for native queries v1.1.2 (2018-05-04) --------------------------- 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 d3ec65805..fb6015698 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -1,705 +1,705 @@ -/* - * 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.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.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -import org.springframework.dao.DataAccessException; -import org.springframework.dao.support.PersistenceExceptionTranslator; -import org.springframework.data.mapping.model.ConvertingPropertyAccessor; - -import com.arangodb.ArangoCollection; -import com.arangodb.ArangoCursor; -import com.arangodb.ArangoDB; -import com.arangodb.ArangoDBException; -import com.arangodb.ArangoDatabase; -import com.arangodb.entity.ArangoDBVersion; -import com.arangodb.entity.DocumentEntity; -import com.arangodb.entity.MultiDocumentEntity; -import com.arangodb.entity.UserEntity; -import com.arangodb.model.AqlQueryOptions; -import com.arangodb.model.CollectionCreateOptions; -import com.arangodb.model.DocumentCreateOptions; -import com.arangodb.model.DocumentDeleteOptions; -import com.arangodb.model.DocumentReadOptions; -import com.arangodb.model.DocumentReplaceOptions; -import com.arangodb.model.DocumentUpdateOptions; -import com.arangodb.model.FulltextIndexOptions; -import com.arangodb.model.GeoIndexOptions; -import com.arangodb.model.HashIndexOptions; -import com.arangodb.model.PersistentIndexOptions; -import com.arangodb.model.SkiplistIndexOptions; -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.CollectionOperations; -import com.arangodb.springframework.core.UserOperations; -import com.arangodb.springframework.core.convert.ArangoConverter; -import com.arangodb.springframework.core.convert.DBCollectionEntity; -import com.arangodb.springframework.core.convert.DBDocumentEntity; -import com.arangodb.springframework.core.convert.DBEntity; -import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; -import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; -import com.arangodb.springframework.core.template.DefaultUserOperation.CollectionCallback; -import com.arangodb.springframework.core.util.ArangoExceptionTranslator; -import com.arangodb.util.MapBuilder; - -/** - * @author Mark Vollmary - * @author Christian Lechner - * - */ -public class ArangoTemplate implements ArangoOperations, CollectionCallback { - - private volatile ArangoDBVersion version; - private final PersistenceExceptionTranslator exceptionTranslator; - private final ArangoConverter converter; - private final ArangoDB arango; - private volatile ArangoDatabase database; - private final String databaseName; - private final Map collectionCache; - - public ArangoTemplate(final ArangoDB.Builder arango, final String database) { - this(arango, database, null); - } - - public ArangoTemplate(final ArangoDB.Builder arango, final String database, final ArangoConverter converter) { - this(arango, database, converter, new ArangoExceptionTranslator()); - } - - public ArangoTemplate(final ArangoDB.Builder arango, final String database, final ArangoConverter converter, - final PersistenceExceptionTranslator exceptionTranslator) { - super(); - this.arango = arango.build()._setCursorInitializer( - new com.arangodb.springframework.core.template.ArangoCursorInitializer(converter)); - this.databaseName = database; - this.converter = converter; - this.exceptionTranslator = exceptionTranslator; - // set concurrency level to 1 as writes are very rare compared to reads - collectionCache = new ConcurrentHashMap<>(8, 0.9f, 1); - version = null; - } - - private ArangoDatabase db() { - // guard against NPE because database can be set to null by dropDatabase() by another thread - ArangoDatabase db = database; - if (db != null) { - return db; - } - // make sure the database is only created once - synchronized (this) { - db = database; - if (db != null) { - return db; - } - db = arango.db(databaseName); - try { - db.getInfo(); - } catch (final ArangoDBException e) { - if (new Integer(404).equals(e.getResponseCode())) { - try { - arango.createDatabase(databaseName); - } catch (final ArangoDBException e1) { - throw translateExceptionIfPossible(e1); - } - } else { - throw translateExceptionIfPossible(e); - } - } - database = db; - return db; - } - } - - private DataAccessException translateExceptionIfPossible(final RuntimeException exception) { - return exceptionTranslator.translateExceptionIfPossible(exception); - } - - private ArangoCollection _collection(final String name) { - return _collection(name, null, null); - } - - private ArangoCollection _collection(final Class entityClass) { - return _collection(entityClass, null); - } - - private ArangoCollection _collection(final Class entityClass, final String id) { - final ArangoPersistentEntity persistentEntity = converter.getMappingContext() - .getRequiredPersistentEntity(entityClass); - final String name = determineCollectionFromId(Optional.ofNullable(id)).orElse(persistentEntity.getCollection()); - return _collection(name, persistentEntity, persistentEntity.getCollectionOptions()); - } - - private ArangoCollection _collection( - final String name, - final ArangoPersistentEntity persistentEntity, - final CollectionCreateOptions options) { - - return collectionCache.computeIfAbsent(name, collName -> { - ArangoCollection collection = db().collection(collName); - try { - collection.getInfo(); - } catch (final ArangoDBException e) { - if (new Integer(404).equals(e.getResponseCode())) { - try { - db().createCollection(collName, options); - } catch (final ArangoDBException e1) { - throw translateExceptionIfPossible(e1); - } - } else { - throw translateExceptionIfPossible(e); - } - } - if (persistentEntity != null) { - ensureCollectionIndexes(collection(collection), persistentEntity); - } - return collection; - }); - } - - private static void ensureCollectionIndexes( - final CollectionOperations collection, - final ArangoPersistentEntity persistentEntity) { - persistentEntity.getHashIndexes().stream().forEach(index -> ensureHashIndex(collection, index)); - persistentEntity.getHashIndexedProperties().stream().forEach(p -> ensureHashIndex(collection, p)); - persistentEntity.getSkiplistIndexes().stream().forEach(index -> ensureSkiplistIndex(collection, index)); - persistentEntity.getSkiplistIndexedProperties().stream().forEach(p -> ensureSkiplistIndex(collection, p)); - persistentEntity.getPersistentIndexes().stream().forEach(index -> ensurePersistentIndex(collection, index)); - persistentEntity.getPersistentIndexedProperties().stream().forEach(p -> ensurePersistentIndex(collection, p)); - persistentEntity.getGeoIndexes().stream().forEach(index -> ensureGeoIndex(collection, index)); - persistentEntity.getGeoIndexedProperties().stream().forEach(p -> ensureGeoIndex(collection, p)); - persistentEntity.getFulltextIndexes().stream().forEach(index -> ensureFulltextIndex(collection, index)); - persistentEntity.getFulltextIndexedProperties().stream().forEach(p -> ensureFulltextIndex(collection, p)); - } - - private static void ensureHashIndex(final CollectionOperations collection, final HashIndex annotation) { - collection.ensureHashIndex(Arrays.asList(annotation.fields()), new HashIndexOptions() - .unique(annotation.unique()).sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); - } - - private static void ensureHashIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { - final HashIndexOptions options = new HashIndexOptions(); - value.getHashIndexed() - .ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); - collection.ensureHashIndex(Collections.singleton(value.getFieldName()), options); - } - - private static void ensureSkiplistIndex(final CollectionOperations collection, final SkiplistIndex annotation) { - collection.ensureSkiplistIndex(Arrays.asList(annotation.fields()), new SkiplistIndexOptions() - .unique(annotation.unique()).sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); - } - - private static void ensureSkiplistIndex( - final CollectionOperations collection, - final ArangoPersistentProperty value) { - final SkiplistIndexOptions options = new SkiplistIndexOptions(); - value.getSkiplistIndexed() - .ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); - collection.ensureSkiplistIndex(Collections.singleton(value.getFieldName()), options); - } - - private static void ensurePersistentIndex(final CollectionOperations collection, final PersistentIndex annotation) { - collection.ensurePersistentIndex(Arrays.asList(annotation.fields()), - new PersistentIndexOptions().unique(annotation.unique()).sparse(annotation.sparse())); - } - - private static void ensurePersistentIndex( - final CollectionOperations collection, - final ArangoPersistentProperty value) { - final PersistentIndexOptions options = new PersistentIndexOptions(); - value.getPersistentIndexed().ifPresent(i -> options.unique(i.unique()).sparse(i.sparse())); - collection.ensurePersistentIndex(Collections.singleton(value.getFieldName()), options); - } - - private static void ensureGeoIndex(final CollectionOperations collection, final GeoIndex annotation) { - collection.ensureGeoIndex(Arrays.asList(annotation.fields()), - new GeoIndexOptions().geoJson(annotation.geoJson())); - } - - private static void ensureGeoIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { - final GeoIndexOptions options = new GeoIndexOptions(); - value.getGeoIndexed().ifPresent(i -> options.geoJson(i.geoJson())); - collection.ensureGeoIndex(Collections.singleton(value.getFieldName()), options); - } - - private static void ensureFulltextIndex(final CollectionOperations collection, final FulltextIndex annotation) { - collection.ensureFulltextIndex(Collections.singleton(annotation.field()), - new FulltextIndexOptions().minLength(annotation.minLength() > -1 ? annotation.minLength() : null)); - } - - private static void ensureFulltextIndex( - final CollectionOperations collection, - 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 Optional determineCollectionFromId(final Optional id) { - return id.map(i -> { - final String[] split = i.split("/"); - return split.length == 2 ? split[0] : null; - }); - } - - private String determineDocumentKeyFromId(final String id) { - final String[] split = id.split("/"); - return split[split.length - 1]; - } - - private DBEntity toDBEntity(final Object value) { - final DBEntity entity = converter.isCollectionType(value.getClass()) ? new DBCollectionEntity() - : new DBDocumentEntity(); - converter.write(value, entity); - return entity; - } - - private T fromDBEntity(final Class entityClass, final DBEntity doc) { - return converter.read(entityClass, doc); - } - - @Override - public ArangoDB driver() { - return arango; - } - - @Override - public ArangoDBVersion getVersion() throws DataAccessException { - try { - if (version == null) { - version = arango.getVersion(); - } - return version; - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public ArangoCursor query(final String query, final AqlQueryOptions options, final Class entityClass) - throws DataAccessException { - return db().query(query, null, options, entityClass); - } - - @Override - public ArangoCursor query( - final String query, - final Map bindVars, - final AqlQueryOptions options, - final Class entityClass) throws DataAccessException { - return db().query(query, - bindVars == null ? null : DBDocumentEntity.class.cast(toDBEntity(prepareBindVars(bindVars))), options, - entityClass); - } - - private Map prepareBindVars(final Map bindVars) { - for (final Map.Entry entry : new HashMap<>(bindVars).entrySet()) { - if (entry.getKey().startsWith("@") && entry.getValue() instanceof Class) { - bindVars.put(entry.getKey(), _collection((Class) entry.getValue()).name()); - } - } - return bindVars; - } - - @Override - public MultiDocumentEntity delete( - final Iterable values, - final Class entityClass, - final DocumentDeleteOptions options) throws DataAccessException { - try { - return _collection(entityClass).deleteDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), - entityClass, options); - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public MultiDocumentEntity delete( - final Iterable values, - final Class entityClass) throws DataAccessException { - return delete(values, entityClass, new DocumentDeleteOptions()); - } - - @Override - public DocumentEntity delete(final String id, final Class entityClass, final DocumentDeleteOptions options) - throws DataAccessException { - try { - return _collection(entityClass, id).deleteDocument(determineDocumentKeyFromId(id), entityClass, options); - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public DocumentEntity delete(final String id, final Class entityClass) throws DataAccessException { - return delete(id, entityClass, new DocumentDeleteOptions()); - } - - @Override - public MultiDocumentEntity update( - final Iterable values, - final Class entityClass, - final DocumentUpdateOptions options) throws DataAccessException { - try { - final MultiDocumentEntity res = _collection(entityClass) - .updateDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); - updateDBFields(values, res); - return res; - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public MultiDocumentEntity update( - final Iterable values, - final Class entityClass) throws DataAccessException { - return update(values, entityClass, new DocumentUpdateOptions()); - } - - @Override - public DocumentEntity update(final String id, final Object value, final DocumentUpdateOptions options) - throws DataAccessException { - try { - final DocumentEntity res = _collection(value.getClass(), id).updateDocument(determineDocumentKeyFromId(id), - toDBEntity(value), options); - updateDBFields(value, res); - return res; - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public DocumentEntity update(final String id, final Object value) throws DataAccessException { - return update(id, value, new DocumentUpdateOptions()); - } - - @Override - public MultiDocumentEntity replace( - final Iterable values, - final Class entityClass, - final DocumentReplaceOptions options) throws DataAccessException { - try { - final MultiDocumentEntity res = _collection(entityClass) - .replaceDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); - updateDBFields(values, res); - return res; - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public MultiDocumentEntity replace( - final Iterable values, - final Class entityClass) throws DataAccessException { - return replace(values, entityClass, new DocumentReplaceOptions()); - } - - @Override - public DocumentEntity replace(final String id, final Object value, final DocumentReplaceOptions options) - throws DataAccessException { - try { - final DocumentEntity res = _collection(value.getClass(), id).replaceDocument(determineDocumentKeyFromId(id), - toDBEntity(value), options); - updateDBFields(value, res); - return res; - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public DocumentEntity replace(final String id, final Object value) throws DataAccessException { - return replace(id, value, new DocumentReplaceOptions()); - } - - @Override - public Optional find(final String id, final Class entityClass, final DocumentReadOptions options) - throws DataAccessException { - try { - final DBEntity doc = _collection(entityClass, id).getDocument(determineDocumentKeyFromId(id), - DBEntity.class, options); - return Optional.ofNullable(fromDBEntity(entityClass, doc)); - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public Optional find(final String id, final Class entityClass) throws DataAccessException { - return find(id, entityClass, new DocumentReadOptions()); - } - - @Override - public Iterable findAll(final Class entityClass) throws DataAccessException { - final String query = "FOR entity IN @@col RETURN entity"; - return new Iterable() { - @Override - public Iterator iterator() { - return query(query, new MapBuilder().put("@col", entityClass).get(), null, entityClass); - } - }; - } - - @Override - public Iterable find(final Iterable ids, final Class entityClass) throws DataAccessException { - try { - final Collection keys = new ArrayList<>(); - ids.forEach(id -> keys.add(determineDocumentKeyFromId(id))); - final MultiDocumentEntity docs = _collection(entityClass).getDocuments(keys, DBEntity.class); - return docs.getDocuments().stream().map(doc -> fromDBEntity(entityClass, doc)).collect(Collectors.toList()); - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public MultiDocumentEntity insert( - final Iterable values, - final Class entityClass, - final DocumentCreateOptions options) throws DataAccessException { - try { - final MultiDocumentEntity res = _collection(entityClass) - .insertDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); - updateDBFields(values, res); - return res; - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public MultiDocumentEntity insert( - final Iterable values, - final Class entityClass) throws DataAccessException { - return insert(values, entityClass, new DocumentCreateOptions()); - } - - @Override - public DocumentEntity insert(final Object value, final DocumentCreateOptions options) throws DataAccessException { - try { - final DocumentEntity res = _collection(value.getClass()).insertDocument(toDBEntity(value), options); - updateDBFields(value, res); - return res; - } catch (final ArangoDBException e) { - throw exceptionTranslator.translateExceptionIfPossible(e); - } - } - - @Override - public DocumentEntity insert(final Object value) throws DataAccessException { - return insert(value, new DocumentCreateOptions()); - } - - @Override - public DocumentEntity insert(final String collectionName, final Object value, final DocumentCreateOptions options) - throws DataAccessException { - try { - final DocumentEntity res = _collection(collectionName).insertDocument(toDBEntity(value), options); - updateDBFields(value, res); - return res; - } catch (final ArangoDBException e) { - throw exceptionTranslator.translateExceptionIfPossible(e); - } - } - - @Override - public DocumentEntity insert(final String collectionName, final Object value) throws DataAccessException { - return insert(collectionName, value, new DocumentCreateOptions()); - } - - @Override - public void upsert(final T value, final UpsertStrategy strategy) throws DataAccessException { - final Class entityClass = value.getClass(); - final ArangoPersistentEntity entity = getConverter().getMappingContext().getPersistentEntity(entityClass); - final ArangoPersistentProperty idProperty = entity.getIdProperty(); - if (idProperty != null) { - final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor( - entity.getPropertyAccessor(value), converter.getConversionService()); - final Object id = accessor.getProperty(idProperty); - if (id != null) { - switch (strategy) { - case UPDATE: - update(id.toString(), value); - break; - case REPLACE: - default: - replace(id.toString(), value); - break; - } - return; - } - } - insert(value); - } - - @SuppressWarnings("unchecked") - @Override - public void upsert(final Iterable value, final UpsertStrategy strategy) throws DataAccessException { - Class entityClass = null; - ArangoPersistentEntity entity = null; - ArangoPersistentProperty idProperty = null; - final Collection withId = new ArrayList<>(); - final Collection withoutId = new ArrayList<>(); - for (final T e : value) { - if (entityClass == null) { - entityClass = (Class) e.getClass(); - entity = getConverter().getMappingContext().getPersistentEntity(entityClass); - idProperty = entity.getIdProperty(); - } - if (idProperty != null) { - final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor( - entity.getPropertyAccessor(e), converter.getConversionService()); - final Object id = accessor.getProperty(idProperty); - if (id != null) { - withId.add(e); - continue; - } - } - withoutId.add(e); - } - if (!withoutId.isEmpty()) { - insert(withoutId, entityClass); - } - if (!withId.isEmpty()) { - switch (strategy) { - case UPDATE: - update(withId, entityClass); - break; - case REPLACE: - default: - replace(withId, entityClass); - break; - } - } - } - - private void updateDBFields(final Iterable values, final MultiDocumentEntity res) { - final Iterator valueIterator = values.iterator(); - if (res.getErrors().isEmpty()) { - final Iterator documentIterator = res.getDocuments().iterator(); - for (; valueIterator.hasNext() && documentIterator.hasNext();) { - updateDBFields(valueIterator.next(), documentIterator.next()); - } - } else { - final Iterator documentIterator = res.getDocumentsAndErrors().iterator(); - for (; valueIterator.hasNext() && documentIterator.hasNext();) { - final Object nextDoc = documentIterator.next(); - final Object nextValue = valueIterator.next(); - if (nextDoc instanceof DocumentEntity) { - updateDBFields(nextValue, (DocumentEntity) nextDoc); - } - } - } - } - - private void updateDBFields(final Object value, final DocumentEntity documentEntity) { - final ArangoPersistentEntity entity = converter.getMappingContext().getPersistentEntity(value.getClass()); - final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(value), - converter.getConversionService()); - final ArangoPersistentProperty idProperty = entity.getIdProperty(); - if (idProperty != null) { - accessor.setProperty(idProperty, documentEntity.getId()); - } - entity.getKeyProperty().ifPresent(key -> accessor.setProperty(key, documentEntity.getKey())); - entity.getRevProperty().ifPresent(rev -> accessor.setProperty(rev, documentEntity.getRev())); - } - - @Override - public boolean exists(final String id, final Class entityClass) throws DataAccessException { - try { - return _collection(entityClass).documentExists(determineDocumentKeyFromId(id)); - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public void dropDatabase() throws DataAccessException { - // guard against NPE because another thread could also call dropDatabase() - ArangoDatabase db = database; - if (db == null) { - db = arango.db(databaseName); - } - try { - db.drop(); - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - database = null; - collectionCache.clear(); - } - - @Override - public CollectionOperations collection(final Class entityClass) throws DataAccessException { - return collection(_collection(entityClass)); - } - - @Override - public CollectionOperations collection(final String name) throws DataAccessException { - return collection(_collection(name)); - } - - @Override - public CollectionOperations collection(final String name, final CollectionCreateOptions options) - throws DataAccessException { - return collection(_collection(name, null, options)); - } - - private CollectionOperations collection(final ArangoCollection collection) { - return new DefaultCollectionOperations(collection, collectionCache, exceptionTranslator); - } - - @Override - public UserOperations user(final String username) { - return new DefaultUserOperation(db(), username, exceptionTranslator, this); - } - - @Override - public Iterable getUsers() throws DataAccessException { - try { - return arango.getUsers(); - } catch (final ArangoDBException e) { - throw translateExceptionIfPossible(e); - } - } - - @Override - public ArangoConverter getConverter() { - return this.converter; - } - -} +/* + * 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.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.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import org.springframework.dao.DataAccessException; +import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.mapping.model.ConvertingPropertyAccessor; + +import com.arangodb.ArangoCollection; +import com.arangodb.ArangoCursor; +import com.arangodb.ArangoDB; +import com.arangodb.ArangoDBException; +import com.arangodb.ArangoDatabase; +import com.arangodb.entity.ArangoDBVersion; +import com.arangodb.entity.DocumentEntity; +import com.arangodb.entity.MultiDocumentEntity; +import com.arangodb.entity.UserEntity; +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.model.CollectionCreateOptions; +import com.arangodb.model.DocumentCreateOptions; +import com.arangodb.model.DocumentDeleteOptions; +import com.arangodb.model.DocumentReadOptions; +import com.arangodb.model.DocumentReplaceOptions; +import com.arangodb.model.DocumentUpdateOptions; +import com.arangodb.model.FulltextIndexOptions; +import com.arangodb.model.GeoIndexOptions; +import com.arangodb.model.HashIndexOptions; +import com.arangodb.model.PersistentIndexOptions; +import com.arangodb.model.SkiplistIndexOptions; +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.CollectionOperations; +import com.arangodb.springframework.core.UserOperations; +import com.arangodb.springframework.core.convert.ArangoConverter; +import com.arangodb.springframework.core.convert.DBCollectionEntity; +import com.arangodb.springframework.core.convert.DBDocumentEntity; +import com.arangodb.springframework.core.convert.DBEntity; +import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; +import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; +import com.arangodb.springframework.core.template.DefaultUserOperation.CollectionCallback; +import com.arangodb.springframework.core.util.ArangoExceptionTranslator; +import com.arangodb.util.MapBuilder; + +/** + * @author Mark Vollmary + * @author Christian Lechner + * + */ +public class ArangoTemplate implements ArangoOperations, CollectionCallback { + + private volatile ArangoDBVersion version; + private final PersistenceExceptionTranslator exceptionTranslator; + private final ArangoConverter converter; + private final ArangoDB arango; + private volatile ArangoDatabase database; + private final String databaseName; + private final Map collectionCache; + + public ArangoTemplate(final ArangoDB.Builder arango, final String database) { + this(arango, database, null); + } + + public ArangoTemplate(final ArangoDB.Builder arango, final String database, final ArangoConverter converter) { + this(arango, database, converter, new ArangoExceptionTranslator()); + } + + public ArangoTemplate(final ArangoDB.Builder arango, final String database, final ArangoConverter converter, + final PersistenceExceptionTranslator exceptionTranslator) { + super(); + this.arango = arango.build()._setCursorInitializer( + new com.arangodb.springframework.core.template.ArangoCursorInitializer(converter)); + this.databaseName = database; + this.converter = converter; + this.exceptionTranslator = exceptionTranslator; + // set concurrency level to 1 as writes are very rare compared to reads + collectionCache = new ConcurrentHashMap<>(8, 0.9f, 1); + version = null; + } + + private ArangoDatabase db() { + // guard against NPE because database can be set to null by dropDatabase() by another thread + ArangoDatabase db = database; + if (db != null) { + return db; + } + // make sure the database is only created once + synchronized (this) { + db = database; + if (db != null) { + return db; + } + db = arango.db(databaseName); + try { + db.getInfo(); + } catch (final ArangoDBException e) { + if (new Integer(404).equals(e.getResponseCode())) { + try { + arango.createDatabase(databaseName); + } catch (final ArangoDBException e1) { + throw translateExceptionIfPossible(e1); + } + } else { + throw translateExceptionIfPossible(e); + } + } + database = db; + return db; + } + } + + private DataAccessException translateExceptionIfPossible(final RuntimeException exception) { + return exceptionTranslator.translateExceptionIfPossible(exception); + } + + private ArangoCollection _collection(final String name) { + return _collection(name, null, null); + } + + private ArangoCollection _collection(final Class entityClass) { + return _collection(entityClass, null); + } + + private ArangoCollection _collection(final Class entityClass, final String id) { + final ArangoPersistentEntity persistentEntity = converter.getMappingContext() + .getPersistentEntity(entityClass); + final String name = determineCollectionFromId(Optional.ofNullable(id)).orElse(persistentEntity.getCollection()); + return _collection(name, persistentEntity, persistentEntity.getCollectionOptions()); + } + + private ArangoCollection _collection( + final String name, + final ArangoPersistentEntity persistentEntity, + final CollectionCreateOptions options) { + + return collectionCache.computeIfAbsent(name, collName -> { + final ArangoCollection collection = db().collection(collName); + try { + collection.getInfo(); + } catch (final ArangoDBException e) { + if (new Integer(404).equals(e.getResponseCode())) { + try { + db().createCollection(collName, options); + } catch (final ArangoDBException e1) { + throw translateExceptionIfPossible(e1); + } + } else { + throw translateExceptionIfPossible(e); + } + } + if (persistentEntity != null) { + ensureCollectionIndexes(collection(collection), persistentEntity); + } + return collection; + }); + } + + private static void ensureCollectionIndexes( + final CollectionOperations collection, + final ArangoPersistentEntity persistentEntity) { + persistentEntity.getHashIndexes().stream().forEach(index -> ensureHashIndex(collection, index)); + persistentEntity.getHashIndexedProperties().stream().forEach(p -> ensureHashIndex(collection, p)); + persistentEntity.getSkiplistIndexes().stream().forEach(index -> ensureSkiplistIndex(collection, index)); + persistentEntity.getSkiplistIndexedProperties().stream().forEach(p -> ensureSkiplistIndex(collection, p)); + persistentEntity.getPersistentIndexes().stream().forEach(index -> ensurePersistentIndex(collection, index)); + persistentEntity.getPersistentIndexedProperties().stream().forEach(p -> ensurePersistentIndex(collection, p)); + persistentEntity.getGeoIndexes().stream().forEach(index -> ensureGeoIndex(collection, index)); + persistentEntity.getGeoIndexedProperties().stream().forEach(p -> ensureGeoIndex(collection, p)); + persistentEntity.getFulltextIndexes().stream().forEach(index -> ensureFulltextIndex(collection, index)); + persistentEntity.getFulltextIndexedProperties().stream().forEach(p -> ensureFulltextIndex(collection, p)); + } + + private static void ensureHashIndex(final CollectionOperations collection, final HashIndex annotation) { + collection.ensureHashIndex(Arrays.asList(annotation.fields()), new HashIndexOptions() + .unique(annotation.unique()).sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); + } + + private static void ensureHashIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { + final HashIndexOptions options = new HashIndexOptions(); + value.getHashIndexed() + .ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); + collection.ensureHashIndex(Collections.singleton(value.getFieldName()), options); + } + + private static void ensureSkiplistIndex(final CollectionOperations collection, final SkiplistIndex annotation) { + collection.ensureSkiplistIndex(Arrays.asList(annotation.fields()), new SkiplistIndexOptions() + .unique(annotation.unique()).sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); + } + + private static void ensureSkiplistIndex( + final CollectionOperations collection, + final ArangoPersistentProperty value) { + final SkiplistIndexOptions options = new SkiplistIndexOptions(); + value.getSkiplistIndexed() + .ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); + collection.ensureSkiplistIndex(Collections.singleton(value.getFieldName()), options); + } + + private static void ensurePersistentIndex(final CollectionOperations collection, final PersistentIndex annotation) { + collection.ensurePersistentIndex(Arrays.asList(annotation.fields()), + new PersistentIndexOptions().unique(annotation.unique()).sparse(annotation.sparse())); + } + + private static void ensurePersistentIndex( + final CollectionOperations collection, + final ArangoPersistentProperty value) { + final PersistentIndexOptions options = new PersistentIndexOptions(); + value.getPersistentIndexed().ifPresent(i -> options.unique(i.unique()).sparse(i.sparse())); + collection.ensurePersistentIndex(Collections.singleton(value.getFieldName()), options); + } + + private static void ensureGeoIndex(final CollectionOperations collection, final GeoIndex annotation) { + collection.ensureGeoIndex(Arrays.asList(annotation.fields()), + new GeoIndexOptions().geoJson(annotation.geoJson())); + } + + private static void ensureGeoIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { + final GeoIndexOptions options = new GeoIndexOptions(); + value.getGeoIndexed().ifPresent(i -> options.geoJson(i.geoJson())); + collection.ensureGeoIndex(Collections.singleton(value.getFieldName()), options); + } + + private static void ensureFulltextIndex(final CollectionOperations collection, final FulltextIndex annotation) { + collection.ensureFulltextIndex(Collections.singleton(annotation.field()), + new FulltextIndexOptions().minLength(annotation.minLength() > -1 ? annotation.minLength() : null)); + } + + private static void ensureFulltextIndex( + final CollectionOperations collection, + 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 Optional determineCollectionFromId(final Optional id) { + return id.map(i -> { + final String[] split = i.split("/"); + return split.length == 2 ? split[0] : null; + }); + } + + private String determineDocumentKeyFromId(final String id) { + final String[] split = id.split("/"); + return split[split.length - 1]; + } + + private DBEntity toDBEntity(final Object value) { + final DBEntity entity = converter.isCollectionType(value.getClass()) ? new DBCollectionEntity() + : new DBDocumentEntity(); + converter.write(value, entity); + return entity; + } + + private T fromDBEntity(final Class entityClass, final DBEntity doc) { + return converter.read(entityClass, doc); + } + + @Override + public ArangoDB driver() { + return arango; + } + + @Override + public ArangoDBVersion getVersion() throws DataAccessException { + try { + if (version == null) { + version = arango.getVersion(); + } + return version; + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public ArangoCursor query(final String query, final AqlQueryOptions options, final Class entityClass) + throws DataAccessException { + return db().query(query, null, options, entityClass); + } + + @Override + public ArangoCursor query( + final String query, + final Map bindVars, + final AqlQueryOptions options, + final Class entityClass) throws DataAccessException { + return db().query(query, + bindVars == null ? null : DBDocumentEntity.class.cast(toDBEntity(prepareBindVars(bindVars))), options, + entityClass); + } + + private Map prepareBindVars(final Map bindVars) { + for (final Map.Entry entry : new HashMap<>(bindVars).entrySet()) { + if (entry.getKey().startsWith("@") && entry.getValue() instanceof Class) { + bindVars.put(entry.getKey(), _collection((Class) entry.getValue()).name()); + } + } + return bindVars; + } + + @Override + public MultiDocumentEntity delete( + final Iterable values, + final Class entityClass, + final DocumentDeleteOptions options) throws DataAccessException { + try { + return _collection(entityClass).deleteDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), + entityClass, options); + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public MultiDocumentEntity delete( + final Iterable values, + final Class entityClass) throws DataAccessException { + return delete(values, entityClass, new DocumentDeleteOptions()); + } + + @Override + public DocumentEntity delete(final String id, final Class entityClass, final DocumentDeleteOptions options) + throws DataAccessException { + try { + return _collection(entityClass, id).deleteDocument(determineDocumentKeyFromId(id), entityClass, options); + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public DocumentEntity delete(final String id, final Class entityClass) throws DataAccessException { + return delete(id, entityClass, new DocumentDeleteOptions()); + } + + @Override + public MultiDocumentEntity update( + final Iterable values, + final Class entityClass, + final DocumentUpdateOptions options) throws DataAccessException { + try { + final MultiDocumentEntity res = _collection(entityClass) + .updateDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); + updateDBFields(values, res); + return res; + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public MultiDocumentEntity update( + final Iterable values, + final Class entityClass) throws DataAccessException { + return update(values, entityClass, new DocumentUpdateOptions()); + } + + @Override + public DocumentEntity update(final String id, final Object value, final DocumentUpdateOptions options) + throws DataAccessException { + try { + final DocumentEntity res = _collection(value.getClass(), id).updateDocument(determineDocumentKeyFromId(id), + toDBEntity(value), options); + updateDBFields(value, res); + return res; + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public DocumentEntity update(final String id, final Object value) throws DataAccessException { + return update(id, value, new DocumentUpdateOptions()); + } + + @Override + public MultiDocumentEntity replace( + final Iterable values, + final Class entityClass, + final DocumentReplaceOptions options) throws DataAccessException { + try { + final MultiDocumentEntity res = _collection(entityClass) + .replaceDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); + updateDBFields(values, res); + return res; + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public MultiDocumentEntity replace( + final Iterable values, + final Class entityClass) throws DataAccessException { + return replace(values, entityClass, new DocumentReplaceOptions()); + } + + @Override + public DocumentEntity replace(final String id, final Object value, final DocumentReplaceOptions options) + throws DataAccessException { + try { + final DocumentEntity res = _collection(value.getClass(), id).replaceDocument(determineDocumentKeyFromId(id), + toDBEntity(value), options); + updateDBFields(value, res); + return res; + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public DocumentEntity replace(final String id, final Object value) throws DataAccessException { + return replace(id, value, new DocumentReplaceOptions()); + } + + @Override + public Optional find(final String id, final Class entityClass, final DocumentReadOptions options) + throws DataAccessException { + try { + final DBEntity doc = _collection(entityClass, id).getDocument(determineDocumentKeyFromId(id), + DBEntity.class, options); + return Optional.ofNullable(fromDBEntity(entityClass, doc)); + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public Optional find(final String id, final Class entityClass) throws DataAccessException { + return find(id, entityClass, new DocumentReadOptions()); + } + + @Override + public Iterable findAll(final Class entityClass) throws DataAccessException { + final String query = "FOR entity IN @@col RETURN entity"; + return new Iterable() { + @Override + public Iterator iterator() { + return query(query, new MapBuilder().put("@col", entityClass).get(), null, entityClass); + } + }; + } + + @Override + public Iterable find(final Iterable ids, final Class entityClass) throws DataAccessException { + try { + final Collection keys = new ArrayList<>(); + ids.forEach(id -> keys.add(determineDocumentKeyFromId(id))); + final MultiDocumentEntity docs = _collection(entityClass).getDocuments(keys, DBEntity.class); + return docs.getDocuments().stream().map(doc -> fromDBEntity(entityClass, doc)).collect(Collectors.toList()); + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public MultiDocumentEntity insert( + final Iterable values, + final Class entityClass, + final DocumentCreateOptions options) throws DataAccessException { + try { + final MultiDocumentEntity res = _collection(entityClass) + .insertDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); + updateDBFields(values, res); + return res; + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public MultiDocumentEntity insert( + final Iterable values, + final Class entityClass) throws DataAccessException { + return insert(values, entityClass, new DocumentCreateOptions()); + } + + @Override + public DocumentEntity insert(final Object value, final DocumentCreateOptions options) throws DataAccessException { + try { + final DocumentEntity res = _collection(value.getClass()).insertDocument(toDBEntity(value), options); + updateDBFields(value, res); + return res; + } catch (final ArangoDBException e) { + throw exceptionTranslator.translateExceptionIfPossible(e); + } + } + + @Override + public DocumentEntity insert(final Object value) throws DataAccessException { + return insert(value, new DocumentCreateOptions()); + } + + @Override + public DocumentEntity insert(final String collectionName, final Object value, final DocumentCreateOptions options) + throws DataAccessException { + try { + final DocumentEntity res = _collection(collectionName).insertDocument(toDBEntity(value), options); + updateDBFields(value, res); + return res; + } catch (final ArangoDBException e) { + throw exceptionTranslator.translateExceptionIfPossible(e); + } + } + + @Override + public DocumentEntity insert(final String collectionName, final Object value) throws DataAccessException { + return insert(collectionName, value, new DocumentCreateOptions()); + } + + @Override + public void upsert(final T value, final UpsertStrategy strategy) throws DataAccessException { + final Class entityClass = value.getClass(); + final ArangoPersistentEntity entity = getConverter().getMappingContext().getPersistentEntity(entityClass); + final ArangoPersistentProperty idProperty = entity.getIdProperty(); + if (idProperty != null) { + final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor( + entity.getPropertyAccessor(value), converter.getConversionService()); + final Object id = accessor.getProperty(idProperty); + if (id != null) { + switch (strategy) { + case UPDATE: + update(id.toString(), value); + break; + case REPLACE: + default: + replace(id.toString(), value); + break; + } + return; + } + } + insert(value); + } + + @SuppressWarnings("unchecked") + @Override + public void upsert(final Iterable value, final UpsertStrategy strategy) throws DataAccessException { + Class entityClass = null; + ArangoPersistentEntity entity = null; + ArangoPersistentProperty idProperty = null; + final Collection withId = new ArrayList<>(); + final Collection withoutId = new ArrayList<>(); + for (final T e : value) { + if (entityClass == null) { + entityClass = (Class) e.getClass(); + entity = getConverter().getMappingContext().getPersistentEntity(entityClass); + idProperty = entity.getIdProperty(); + } + if (idProperty != null) { + final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor( + entity.getPropertyAccessor(e), converter.getConversionService()); + final Object id = accessor.getProperty(idProperty); + if (id != null) { + withId.add(e); + continue; + } + } + withoutId.add(e); + } + if (!withoutId.isEmpty()) { + insert(withoutId, entityClass); + } + if (!withId.isEmpty()) { + switch (strategy) { + case UPDATE: + update(withId, entityClass); + break; + case REPLACE: + default: + replace(withId, entityClass); + break; + } + } + } + + private void updateDBFields(final Iterable values, final MultiDocumentEntity res) { + final Iterator valueIterator = values.iterator(); + if (res.getErrors().isEmpty()) { + final Iterator documentIterator = res.getDocuments().iterator(); + for (; valueIterator.hasNext() && documentIterator.hasNext();) { + updateDBFields(valueIterator.next(), documentIterator.next()); + } + } else { + final Iterator documentIterator = res.getDocumentsAndErrors().iterator(); + for (; valueIterator.hasNext() && documentIterator.hasNext();) { + final Object nextDoc = documentIterator.next(); + final Object nextValue = valueIterator.next(); + if (nextDoc instanceof DocumentEntity) { + updateDBFields(nextValue, (DocumentEntity) nextDoc); + } + } + } + } + + private void updateDBFields(final Object value, final DocumentEntity documentEntity) { + final ArangoPersistentEntity entity = converter.getMappingContext().getPersistentEntity(value.getClass()); + final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(value), + converter.getConversionService()); + final ArangoPersistentProperty idProperty = entity.getIdProperty(); + if (idProperty != null) { + accessor.setProperty(idProperty, documentEntity.getId()); + } + entity.getKeyProperty().ifPresent(key -> accessor.setProperty(key, documentEntity.getKey())); + entity.getRevProperty().ifPresent(rev -> accessor.setProperty(rev, documentEntity.getRev())); + } + + @Override + public boolean exists(final String id, final Class entityClass) throws DataAccessException { + try { + return _collection(entityClass).documentExists(determineDocumentKeyFromId(id)); + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public void dropDatabase() throws DataAccessException { + // guard against NPE because another thread could also call dropDatabase() + ArangoDatabase db = database; + if (db == null) { + db = arango.db(databaseName); + } + try { + db.drop(); + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + database = null; + collectionCache.clear(); + } + + @Override + public CollectionOperations collection(final Class entityClass) throws DataAccessException { + return collection(_collection(entityClass)); + } + + @Override + public CollectionOperations collection(final String name) throws DataAccessException { + return collection(_collection(name)); + } + + @Override + public CollectionOperations collection(final String name, final CollectionCreateOptions options) + throws DataAccessException { + return collection(_collection(name, null, options)); + } + + private CollectionOperations collection(final ArangoCollection collection) { + return new DefaultCollectionOperations(collection, collectionCache, exceptionTranslator); + } + + @Override + public UserOperations user(final String username) { + return new DefaultUserOperation(db(), username, exceptionTranslator, this); + } + + @Override + public Iterable getUsers() throws DataAccessException { + try { + return arango.getUsers(); + } catch (final ArangoDBException e) { + throw translateExceptionIfPossible(e); + } + } + + @Override + public ArangoConverter getConverter() { + return this.converter; + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/util/AqlUtils.java b/src/main/java/com/arangodb/springframework/core/util/AqlUtils.java index 80fcd384a..8d32b7a8e 100644 --- a/src/main/java/com/arangodb/springframework/core/util/AqlUtils.java +++ b/src/main/java/com/arangodb/springframework/core/util/AqlUtils.java @@ -1,200 +1,196 @@ -/* - * 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.util; - -import java.util.StringJoiner; - -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.lang.Nullable; -import org.springframework.util.StringUtils; - -/** - * - * @author Christian Lechner - */ -public final class AqlUtils { - - private AqlUtils() { - - } - - public static String buildLimitClause(final Pageable pageable) { - if (pageable.isUnpaged()) { - return ""; - } - - final StringJoiner clause = new StringJoiner(", ", "LIMIT ", ""); - clause.add(String.valueOf(pageable.getOffset())); - clause.add(String.valueOf(pageable.getPageSize())); - return clause.toString(); - } - - public static String buildPageableClause(final Pageable pageable) { - return buildPageableClause(pageable, null); - } - - public static String buildPageableClause(final Pageable pageable, @Nullable final String varName) { - return buildPageableClause(pageable, varName, new StringBuilder()).toString(); - } - - private static StringBuilder buildPageableClause( - final Pageable pageable, - @Nullable final String varName, - final StringBuilder clause) { - - if (pageable.isUnpaged()) { - return clause; - } - - Sort sort = pageable.getSort(); - buildSortClause(sort, varName, clause); - - if (sort.isSorted()) { - clause.append(' '); - } - - clause.append("LIMIT ").append(pageable.getOffset()).append(", ").append(pageable.getPageSize()); - return clause; - } - - public static String buildSortClause(final Sort sort) { - return buildSortClause(sort, null); - } - - public static String buildSortClause(final Sort sort, @Nullable final String varName) { - return buildSortClause(sort, varName, new StringBuilder()).toString(); - } - - private static StringBuilder buildSortClause( - final Sort sort, - @Nullable final String varName, - final StringBuilder clause) { - - if (sort.isUnsorted()) { - return clause; - } - - final String prefix = StringUtils.hasText(varName) ? escapeSortProperty(varName) : null; - clause.append("SORT "); - boolean first = true; - - for (final Sort.Order order : sort) { - if (!first) { - clause.append(", "); - } else { - first = false; - } - - if (prefix != null) { - clause.append(prefix).append('.'); - } - final String escapedProperty = escapeSortProperty(order.getProperty()); - clause.append(escapedProperty).append(' ').append(order.getDirection()); - } - return clause; - - } - - private static String escapeSortProperty(final String str) { - // dots are not allowed at start/end - if (str.charAt(0) == '.' || str.charAt(str.length() - 1) == '.') { - throw new IllegalArgumentException("Sort properties must not begin or end with a dot!"); - } - - final StringBuilder escaped = new StringBuilder(); - escaped.append('`'); - - // keep track if we are inside an escaped sequence - boolean inEscapedSeq = false; - - for (int i = 0; i < str.length(); ++i) { - final char currChar = str.charAt(i); - final boolean hasNext = (i + 1) < str.length(); - final char nextChar = hasNext ? str.charAt(i + 1) : '\0'; - - if (currChar == '\\') { - // keep escaped backticks - if (nextChar == '`') { - escaped.append("\\`"); - ++i; - } - // escape backslashes - else { - escaped.append("\\\\"); - } - } - - // current char is an unescaped backtick - else if (currChar == '`') { - inEscapedSeq = !inEscapedSeq; - - final boolean isStartOrEnd = i == 0 || !hasNext; - final boolean isNextCharDotOutsideEscapedSeq = nextChar == '.' && !inEscapedSeq; - - // unescaped backticks are only allowed at start/end of attributes - if (!isStartOrEnd && !isNextCharDotOutsideEscapedSeq) { - throw new IllegalArgumentException( - "Sort properties must only contain backticks at beginning/end of attributes or when escaped."); - } - } - - else if (currChar == '.') { - // the dot is part of an attribute name when inside escaped sequence - if (inEscapedSeq) { - // add dot without escaping - escaped.append('.'); - } - - else { - // properties can only contain 2+ dots in escaped sequences - if (nextChar == '.') { - throw new IllegalArgumentException( - "Sort properties may not contain 2+ consecutive dots when outside a backtick escape sequence!"); - } - // consume optional backtick - else if (nextChar == '`') { - inEscapedSeq = !inEscapedSeq; - ++i; - } - - // close previous escape sequence and open new one - escaped.append("`.`"); - } - } - - // keep others - else { - escaped.append(currChar); - } - } - - // check for an open escape sequence - if (inEscapedSeq) { - throw new IllegalArgumentException( - "A sort property contains an unclosed backtick escape sequence! The cause may be a missing backtick."); - } - - escaped.append('`'); - return escaped.toString(); - } - -} +/* + * 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.util; + +import java.util.StringJoiner; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.util.StringUtils; + +/** + * + * @author Christian Lechner + */ +public final class AqlUtils { + + private AqlUtils() { + + } + + public static String buildLimitClause(final Pageable pageable) { + if (pageable == null) { + return ""; + } + + final StringJoiner clause = new StringJoiner(", ", "LIMIT ", ""); + clause.add(String.valueOf(pageable.getOffset())); + clause.add(String.valueOf(pageable.getPageSize())); + return clause.toString(); + } + + public static String buildPageableClause(final Pageable pageable) { + return buildPageableClause(pageable, null); + } + + public static String buildPageableClause(final Pageable pageable, final String varName) { + return buildPageableClause(pageable, varName, new StringBuilder()).toString(); + } + + private static StringBuilder buildPageableClause( + final Pageable pageable, + final String varName, + final StringBuilder clause) { + + if (pageable == null) { + return clause; + } + + final Sort sort = pageable.getSort(); + buildSortClause(sort, varName, clause); + + if (sort != null) { + clause.append(' '); + } + + clause.append("LIMIT ").append(pageable.getOffset()).append(", ").append(pageable.getPageSize()); + return clause; + } + + public static String buildSortClause(final Sort sort) { + return buildSortClause(sort, null); + } + + public static String buildSortClause(final Sort sort, final String varName) { + return buildSortClause(sort, varName, new StringBuilder()).toString(); + } + + private static StringBuilder buildSortClause(final Sort sort, final String varName, final StringBuilder clause) { + + if (sort == null) { + return clause; + } + + final String prefix = StringUtils.hasText(varName) ? escapeSortProperty(varName) : null; + clause.append("SORT "); + boolean first = true; + + for (final Sort.Order order : sort) { + if (!first) { + clause.append(", "); + } else { + first = false; + } + + if (prefix != null) { + clause.append(prefix).append('.'); + } + final String escapedProperty = escapeSortProperty(order.getProperty()); + clause.append(escapedProperty).append(' ').append(order.getDirection()); + } + return clause; + + } + + private static String escapeSortProperty(final String str) { + // dots are not allowed at start/end + if (str.charAt(0) == '.' || str.charAt(str.length() - 1) == '.') { + throw new IllegalArgumentException("Sort properties must not begin or end with a dot!"); + } + + final StringBuilder escaped = new StringBuilder(); + escaped.append('`'); + + // keep track if we are inside an escaped sequence + boolean inEscapedSeq = false; + + for (int i = 0; i < str.length(); ++i) { + final char currChar = str.charAt(i); + final boolean hasNext = (i + 1) < str.length(); + final char nextChar = hasNext ? str.charAt(i + 1) : '\0'; + + if (currChar == '\\') { + // keep escaped backticks + if (nextChar == '`') { + escaped.append("\\`"); + ++i; + } + // escape backslashes + else { + escaped.append("\\\\"); + } + } + + // current char is an unescaped backtick + else if (currChar == '`') { + inEscapedSeq = !inEscapedSeq; + + final boolean isStartOrEnd = i == 0 || !hasNext; + final boolean isNextCharDotOutsideEscapedSeq = nextChar == '.' && !inEscapedSeq; + + // unescaped backticks are only allowed at start/end of attributes + if (!isStartOrEnd && !isNextCharDotOutsideEscapedSeq) { + throw new IllegalArgumentException( + "Sort properties must only contain backticks at beginning/end of attributes or when escaped."); + } + } + + else if (currChar == '.') { + // the dot is part of an attribute name when inside escaped sequence + if (inEscapedSeq) { + // add dot without escaping + escaped.append('.'); + } + + else { + // properties can only contain 2+ dots in escaped sequences + if (nextChar == '.') { + throw new IllegalArgumentException( + "Sort properties may not contain 2+ consecutive dots when outside a backtick escape sequence!"); + } + // consume optional backtick + else if (nextChar == '`') { + inEscapedSeq = !inEscapedSeq; + ++i; + } + + // close previous escape sequence and open new one + escaped.append("`.`"); + } + } + + // keep others + else { + escaped.append(currChar); + } + } + + // check for an open escape sequence + if (inEscapedSeq) { + throw new IllegalArgumentException( + "A sort property contains an unclosed backtick escape sequence! The cause may be a missing backtick."); + } + + escaped.append('`'); + return escaped.toString(); + } + +} diff --git a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java index 173a3cebd..d7d667015 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java +++ b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java @@ -91,9 +91,9 @@ public class DerivedQueryCreator extends AbstractQueryCreator, ArangoPersistentProperty> context, - final Class domainClass, final PartTree tree, final ArangoParameterAccessor accessor, - final Map bindVars, final List geoFields, final boolean useFunctions) { + final MappingContext, ArangoPersistentProperty> context, + final Class domainClass, final PartTree tree, final ArangoParameterAccessor accessor, + final Map bindVars, final List geoFields, final boolean useFunctions) { super(tree, accessor); this.context = context; this.collectionName = collectionName(context.getPersistentEntity(domainClass).getCollection()); @@ -144,8 +144,8 @@ protected ConjunctionBuilder or(final ConjunctionBuilder base, final Conjunction } /** - * Builds a full AQL query from a built Disjunction, additional information from - * PartTree and special parameters caught by ArangoParameterAccessor + * Builds a full AQL query from a built Disjunction, additional information from PartTree and special parameters + * caught by ArangoParameterAccessor * * @param criteria * @param sort @@ -161,19 +161,19 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { } final Disjunction disjunction = disjunctionBuilder.build(); final String array = disjunction.getArray().length() == 0 ? collectionName : disjunction.getArray(); - final String predicate = disjunction.getPredicate().length() == 0 ? "" : " FILTER " + disjunction.getPredicate(); + final String predicate = disjunction.getPredicate().length() == 0 ? "" + : " FILTER " + disjunction.getPredicate(); final String queryTemplate = "%sFOR e IN %s%s%s%s%s%s%s"; // collection predicate count sort limit pageable // queryType final String count = (tree.isCountProjection() || tree.isExistsProjection()) - ? ((tree.isDistinct() ? " COLLECT entity = e" : "") + " COLLECT WITH COUNT INTO length") - : ""; + ? ((tree.isDistinct() ? " COLLECT entity = e" : "") + " COLLECT WITH COUNT INTO length") : ""; final String limit = tree.isLimiting() ? format(" LIMIT %d", tree.getMaxResults()) : ""; final String pageable = accessor.getPageable() == null ? "" : format(" LIMIT %d, %d", accessor.getPageable().getOffset(), accessor.getPageable().getPageSize()); final String geoFields = format("%s[0], %s[1]", uniqueLocation, uniqueLocation); final String distanceAdjusted = getGeoFields().isEmpty() ? "e" : format("MERGE(e, { '_distance': distance(%s, %f, %f) })", geoFields, getUniquePoint()[0], - getUniquePoint()[1]); + getUniquePoint()[1]); final String type = tree.isDelete() ? (" REMOVE e IN " + collectionName) : ((tree.isCountProjection() || tree.isExistsProjection()) ? " RETURN length" : format(" RETURN %s", distanceAdjusted)); @@ -181,8 +181,8 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { if ((!this.geoFields.isEmpty() || isUnique != null && isUnique) && !tree.isDelete() && !tree.isCountProjection() && !tree.isExistsProjection()) { final String distanceSortKey = format(" SORT distance(%s, %f, %f)", geoFields, getUniquePoint()[0], - getUniquePoint()[1]); - if (sortString.length() == 0) { + getUniquePoint()[1]); + if (sort == null) { sortString = distanceSortKey; } else { sortString = distanceSortKey + ", " + sortString.substring(5, sortString.length()); @@ -218,8 +218,7 @@ private String ignorePropertyCase(final Part part) { } /** - * Wrapps property expression in order to lower case. Only properties of type - * String or Iterable are lowered + * Wrapps property expression in order to lower case. Only properties of type String or Iterable are lowered * * @param part * @param property @@ -242,15 +241,14 @@ private String ignorePropertyCase(final Part part, final String property) { * @return */ private String getProperty(final Part part) { - return "e." - + context.getPersistentPropertyPath(part.getProperty()).toPath(null, ArangoPersistentProperty::getFieldName); + return "e." + context.getPersistentPropertyPath(part.getProperty()).toPath(null, + ArangoPersistentProperty::getFieldName); } /** - * Creates a predicate template with one String placeholder for a Part-specific - * predicate expression from properties in PropertyPath which represent - * references or collections, and, also, returns a 2nd String representing - * property to be used in a Part-specific predicate expression + * Creates a predicate template with one String placeholder for a Part-specific predicate expression from properties + * in PropertyPath which represent references or collections, and, also, returns a 2nd String representing property + * to be used in a Part-specific predicate expression * * @param part * @return @@ -305,7 +303,8 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { simpleProperties = new StringBuilder(); final String iteration = format(TEMPLATE, entity, collection, entity, prevEntity, name); final String predicate = format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); + predicateTemplate = predicateTemplate.length() == 0 ? predicate + : format(predicateTemplate, predicate); } else { // collection final String TEMPLATE = "FOR %s IN TO_ARRAY(%s%s)"; @@ -315,7 +314,8 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { simpleProperties = new StringBuilder(); final String iteration = format(TEMPLATE, entity, prevEntity, name); final String predicate = format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); + predicateTemplate = predicateTemplate.length() == 0 ? predicate + : format(predicateTemplate, predicate); } } else { if (property.getRef().isPresent() || property.getFrom().isPresent() || property.getTo().isPresent()) { @@ -331,7 +331,8 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { simpleProperties = new StringBuilder(); final String iteration = format(TEMPLATE, entity, collection, entity, prevEntity, name); final String predicate = format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); + predicateTemplate = predicateTemplate.length() == 0 ? predicate + : format(predicateTemplate, predicate); } else { // simple property simpleProperties.append("." + property.getFieldName()); @@ -343,8 +344,7 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { } /** - * Lowers case of a given argument if its type is String, Iterable or - * String[] if shouldIgnoreCase is true + * Lowers case of a given argument if its type is String, Iterable or String[] if shouldIgnoreCase is true * * @param argument * @param shouldIgnoreCase @@ -374,8 +374,8 @@ private Object ignoreArgumentCase(final Object argument, final boolean shouldIgn } /** - * Determines whether the case for a Part should be ignored based on property - * type and IgnoreCase keywords in the method name + * Determines whether the case for a Part should be ignored based on property type and IgnoreCase keywords in the + * method name * * @param part * @return @@ -393,8 +393,7 @@ private boolean shouldIgnoreCase(final Part part) { } /** - * Puts actual arguments in bindVars Map based on Part-specific information and - * types of arguments. + * Puts actual arguments in bindVars Map based on Part-specific information and types of arguments. * * @param iterator * @param shouldIgnoreCase @@ -403,8 +402,12 @@ private boolean shouldIgnoreCase(final Part part) { * @param ignoreBindVars * @return */ - private ArgumentProcessingResult bindArguments(final Iterator iterator, final boolean shouldIgnoreCase, - final int arguments, final Boolean borderStatus, final boolean ignoreBindVars) { + private ArgumentProcessingResult bindArguments( + final Iterator iterator, + final boolean shouldIgnoreCase, + final int arguments, + final Boolean borderStatus, + final boolean ignoreBindVars) { int bindings = 0; ArgumentProcessingResult.Type type = ArgumentProcessingResult.Type.DEFAULT; for (int i = 0; i < arguments; ++i) { @@ -450,7 +453,8 @@ private ArgumentProcessingResult bindArguments(final Iterator iterator, checkUniquePoint(circle.getCenter()); bindVars.put(Integer.toString(bindingCounter + bindings++), circle.getCenter().getY()); bindVars.put(Integer.toString(bindingCounter + bindings++), circle.getCenter().getX()); - bindVars.put(Integer.toString(bindingCounter + bindings++), convertDistanceToMeters(circle.getRadius())); + bindVars.put(Integer.toString(bindingCounter + bindings++), + convertDistanceToMeters(circle.getRadius())); break; } else if (caseAdjusted.getClass() == Point.class) { final Point point = (Point) caseAdjusted; @@ -481,8 +485,8 @@ private ArgumentProcessingResult bindArguments(final Iterator iterator, } /** - * Ensures that Points used in geospatial parts of non-nested properties are the - * same in case geospatial return type is expected + * Ensures that Points used in geospatial parts of non-nested properties are the same in case geospatial return type + * is expected * * @param point */ @@ -496,7 +500,7 @@ private void checkUniquePoint(final Point point) { } if (!geoFields.isEmpty()) { Assert.isTrue(uniquePoint == null || uniquePoint.equals(point), - "Different Points are used - Distance is ambiguous"); + "Different Points are used - Distance is ambiguous"); uniquePoint = point; } } @@ -518,8 +522,8 @@ private double convertDistanceToMeters(final Distance distance) { } /** - * Ensures that the same geo fields are used in geospatial parts of non-nested - * properties are the same in case geospatial return type is expected + * Ensures that the same geo fields are used in geospatial parts of non-nested properties are the same in case + * geospatial return type is expected * * @param part */ @@ -536,8 +540,8 @@ private void checkUniqueLocation(final Part part) { } /** - * Creates a PartInformation containing a String representing either a predicate - * or array expression, and binds arguments from Iterator for a given Part + * Creates a PartInformation containing a String representing either a predicate or array expression, and binds + * arguments from Iterator for a given Part * * @param part * @param iterator @@ -591,7 +595,7 @@ private PartInformation createPartInformation(final Part part, final Iterator context.getPersistentEntity(t) != null).map(t -> t.getType()).ifPresent(t -> with.add(t)); + .filter(t -> context.getPersistentEntity(t) != null).map(t -> t.getType()) + .ifPresent(t -> with.add(t)); } while ((pp = pp.next()) != null); return clause == null ? null : new PartInformation(isArray, clause, with); } @@ -772,8 +777,7 @@ private String format(final String format, final Object... args) { } /** - * Stores how many bindings where used in a Part and if or what kind of special - * type clause should be created + * Stores how many bindings where used in a Part and if or what kind of special type clause should be created */ private static class ArgumentProcessingResult { diff --git a/src/test/java/com/arangodb/springframework/core/util/AqlUtilsTest.java b/src/test/java/com/arangodb/springframework/core/util/AqlUtilsTest.java index aaf606f8f..d35848e3c 100644 --- a/src/test/java/com/arangodb/springframework/core/util/AqlUtilsTest.java +++ b/src/test/java/com/arangodb/springframework/core/util/AqlUtilsTest.java @@ -1,196 +1,195 @@ -/* - * 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.util; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; - -import org.junit.Assert; -import org.junit.Test; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.domain.Sort.Direction; - -/** - * - * @author Christian Lechner - */ -public class AqlUtilsTest { - - @Test - public void buildLimitClauseTest() { - assertThat(AqlUtils.buildLimitClause(Pageable.unpaged()), is("")); - assertThat(AqlUtils.buildLimitClause(PageRequest.of(0, 1)), is("LIMIT 0, 1")); - assertThat(AqlUtils.buildLimitClause(PageRequest.of(10, 20)), is("LIMIT 200, 20")); - } - - @Test - public void buildPageableClauseTest() { - // Special cases - assertThat(AqlUtils.buildPageableClause(Pageable.unpaged()), is("")); - - // Paging without sort - assertThat(AqlUtils.buildPageableClause(PageRequest.of(0, 1)), is("LIMIT 0, 1")); - assertThat(AqlUtils.buildPageableClause(PageRequest.of(5, 10)), is("LIMIT 50, 10")); - - // Paging with sort - assertThat(AqlUtils.buildPageableClause(PageRequest.of(2, 10, Direction.ASC, "property")), - is("SORT `property` ASC LIMIT 20, 10")); - assertThat(AqlUtils.buildPageableClause(PageRequest.of(2, 10, Direction.ASC, "property"), "var"), - is("SORT `var`.`property` ASC LIMIT 20, 10")); - - assertThat(AqlUtils.buildPageableClause(PageRequest.of(2, 10, Direction.DESC, "property", "property2")), - is("SORT `property` DESC, `property2` DESC LIMIT 20, 10")); - assertThat(AqlUtils.buildPageableClause(PageRequest.of(2, 10, Direction.DESC, "property", "property2"), "var"), - is("SORT `var`.`property` DESC, `var`.`property2` DESC LIMIT 20, 10")); - - assertThat( - AqlUtils.buildPageableClause( - PageRequest.of(2, 10, Sort.by("ascProp").and(Sort.by(Direction.DESC, "descProp")))), - is("SORT `ascProp` ASC, `descProp` DESC LIMIT 20, 10")); - assertThat( - AqlUtils.buildPageableClause( - PageRequest.of(2, 10, Sort.by("ascProp").and(Sort.by(Direction.DESC, "descProp"))), "var"), - is("SORT `var`.`ascProp` ASC, `var`.`descProp` DESC LIMIT 20, 10")); - } - - @Test - public void buildSortClauseTest() { - // Special cases - assertThat(AqlUtils.buildSortClause(Sort.unsorted()), is("")); - - // Others - assertThat(AqlUtils.buildSortClause(Sort.by("property")), is("SORT `property` ASC")); - assertThat(AqlUtils.buildSortClause(Sort.by("property"), "var"), is("SORT `var`.`property` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property")), is("SORT `property` DESC")); - assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property"), "var"), - is("SORT `var`.`property` DESC")); - - assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property", "property2")), - is("SORT `property` DESC, `property2` DESC")); - assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property", "property2"), "var"), - is("SORT `var`.`property` DESC, `var`.`property2` DESC")); - - assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property").and(Sort.by("property2"))), - is("SORT `property` DESC, `property2` ASC")); - assertThat(AqlUtils.buildSortClause(Sort.by(Direction.DESC, "property").and(Sort.by("property2")), "var"), - is("SORT `var`.`property` DESC, `var`.`property2` ASC")); - } - - @Test - public void sortClauseEscapingTest() { - assertThat(AqlUtils.buildSortClause(Sort.by("property")), is("SORT `property` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by("`property`")), is("SORT `property` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by("`pro\\`perty\\``")), is("SORT `pro\\`perty\\`` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by("`dont.split.property`")), is("SORT `dont.split.property` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by("property.`property`")), is("SORT `property`.`property` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by("property.`.`.`property`")), - is("SORT `property`.`.`.`property` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by("property.\\.property")), - is("SORT `property`.`\\\\`.`property` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by("property.\\\\`.property")), - is("SORT `property`.`\\\\\\``.`property` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by("`property.\\`.property`")), - is("SORT `property.\\`.property` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by("`property.\\``.property")), - is("SORT `property.\\``.`property` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by("`property..property`")), is("SORT `property..property` ASC")); - - assertThat(AqlUtils.buildSortClause(Sort.by("property\\. REMOVE doc IN collection //")), - is("SORT `property\\\\`.` REMOVE doc IN collection //` ASC")); - - // Illegal sort properties - - try { - AqlUtils.buildSortClause(Sort.by(".property")); - Assert.fail(); - } catch (IllegalArgumentException e) { - } - - try { - AqlUtils.buildSortClause(Sort.by("property.")); - Assert.fail(); - } catch (IllegalArgumentException e) { - } - - try { - AqlUtils.buildSortClause(Sort.by("property..property")); - Assert.fail(); - } catch (IllegalArgumentException e) { - } - - try { - AqlUtils.buildSortClause(Sort.by("property.`property")); - Assert.fail(); - } catch (IllegalArgumentException e) { - } - - try { - AqlUtils.buildSortClause(Sort.by("pro`perty.property")); - Assert.fail(); - } catch (IllegalArgumentException e) { - } - - try { - AqlUtils.buildSortClause(Sort.by("`property``.property")); - Assert.fail(); - } catch (IllegalArgumentException e) { - } - - try { - AqlUtils.buildSortClause(Sort.by("`property```.property")); - Assert.fail(); - } catch (IllegalArgumentException e) { - } - - try { - AqlUtils.buildSortClause(Sort.by("`property.`\\`.property`")); - Assert.fail(); - } catch (IllegalArgumentException e) { - } - - try { - AqlUtils.buildSortClause(Sort.by("`property.`\\``.property`")); - Assert.fail(); - } catch (IllegalArgumentException e) { - } - - try { - AqlUtils.buildSortClause(Sort.by("`property`.\\``.property`")); - Assert.fail(); - } catch (IllegalArgumentException e) { - } - - } - -} +/* + * 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.util; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; + +/** + * + * @author Christian Lechner + */ +public class AqlUtilsTest { + + @Test + public void buildLimitClauseTest() { + assertThat(AqlUtils.buildLimitClause(null), is("")); + assertThat(AqlUtils.buildLimitClause(new PageRequest(0, 1)), is("LIMIT 0, 1")); + assertThat(AqlUtils.buildLimitClause(new PageRequest(10, 20)), is("LIMIT 200, 20")); + } + + @Test + public void buildPageableClauseTest() { + // Special cases + assertThat(AqlUtils.buildPageableClause(null), is("")); + + // Paging without sort + assertThat(AqlUtils.buildPageableClause(new PageRequest(0, 1)), is("LIMIT 0, 1")); + assertThat(AqlUtils.buildPageableClause(new PageRequest(5, 10)), is("LIMIT 50, 10")); + + // Paging with sort + assertThat(AqlUtils.buildPageableClause(new PageRequest(2, 10, Direction.ASC, "property")), + is("SORT `property` ASC LIMIT 20, 10")); + assertThat(AqlUtils.buildPageableClause(new PageRequest(2, 10, Direction.ASC, "property"), "var"), + is("SORT `var`.`property` ASC LIMIT 20, 10")); + + assertThat(AqlUtils.buildPageableClause(new PageRequest(2, 10, Direction.DESC, "property", "property2")), + is("SORT `property` DESC, `property2` DESC LIMIT 20, 10")); + assertThat(AqlUtils.buildPageableClause(new PageRequest(2, 10, Direction.DESC, "property", "property2"), "var"), + is("SORT `var`.`property` DESC, `var`.`property2` DESC LIMIT 20, 10")); + + assertThat( + AqlUtils.buildPageableClause( + new PageRequest(2, 10, new Sort("ascProp").and(new Sort(Direction.DESC, "descProp")))), + is("SORT `ascProp` ASC, `descProp` DESC LIMIT 20, 10")); + assertThat( + AqlUtils.buildPageableClause( + new PageRequest(2, 10, new Sort("ascProp").and(new Sort(Direction.DESC, "descProp"))), "var"), + is("SORT `var`.`ascProp` ASC, `var`.`descProp` DESC LIMIT 20, 10")); + } + + @Test + public void buildSortClauseTest() { + // Special cases + assertThat(AqlUtils.buildSortClause(null), is("")); + + // Others + assertThat(AqlUtils.buildSortClause(new Sort("property")), is("SORT `property` ASC")); + assertThat(AqlUtils.buildSortClause(new Sort("property"), "var"), is("SORT `var`.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort(Direction.DESC, "property")), is("SORT `property` DESC")); + assertThat(AqlUtils.buildSortClause(new Sort(Direction.DESC, "property"), "var"), + is("SORT `var`.`property` DESC")); + + assertThat(AqlUtils.buildSortClause(new Sort(Direction.DESC, "property", "property2")), + is("SORT `property` DESC, `property2` DESC")); + assertThat(AqlUtils.buildSortClause(new Sort(Direction.DESC, "property", "property2"), "var"), + is("SORT `var`.`property` DESC, `var`.`property2` DESC")); + + assertThat(AqlUtils.buildSortClause(new Sort(Direction.DESC, "property").and(new Sort("property2"))), + is("SORT `property` DESC, `property2` ASC")); + assertThat(AqlUtils.buildSortClause(new Sort(Direction.DESC, "property").and(new Sort("property2")), "var"), + is("SORT `var`.`property` DESC, `var`.`property2` ASC")); + } + + @Test + public void sortClauseEscapingTest() { + assertThat(AqlUtils.buildSortClause(new Sort("property")), is("SORT `property` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort("`property`")), is("SORT `property` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort("`pro\\`perty\\``")), is("SORT `pro\\`perty\\`` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort("`dont.split.property`")), is("SORT `dont.split.property` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort("property.`property`")), is("SORT `property`.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort("property.`.`.`property`")), + is("SORT `property`.`.`.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort("property.\\.property")), + is("SORT `property`.`\\\\`.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort("property.\\\\`.property")), + is("SORT `property`.`\\\\\\``.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort("`property.\\`.property`")), + is("SORT `property.\\`.property` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort("`property.\\``.property")), + is("SORT `property.\\``.`property` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort("`property..property`")), is("SORT `property..property` ASC")); + + assertThat(AqlUtils.buildSortClause(new Sort("property\\. REMOVE doc IN collection //")), + is("SORT `property\\\\`.` REMOVE doc IN collection //` ASC")); + + // Illegal sort properties + + try { + AqlUtils.buildSortClause(new Sort(".property")); + Assert.fail(); + } catch (final IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(new Sort("property.")); + Assert.fail(); + } catch (final IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(new Sort("property..property")); + Assert.fail(); + } catch (final IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(new Sort("property.`property")); + Assert.fail(); + } catch (final IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(new Sort("pro`perty.property")); + Assert.fail(); + } catch (final IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(new Sort("`property``.property")); + Assert.fail(); + } catch (final IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(new Sort("`property```.property")); + Assert.fail(); + } catch (final IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(new Sort("`property.`\\`.property`")); + Assert.fail(); + } catch (final IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(new Sort("`property.`\\``.property`")); + Assert.fail(); + } catch (final IllegalArgumentException e) { + } + + try { + AqlUtils.buildSortClause(new Sort("`property`.\\``.property`")); + Assert.fail(); + } catch (final IllegalArgumentException e) { + } + + } + +} diff --git a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java index aaad150b8..2f1accc19 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java @@ -46,7 +46,8 @@ public class ArangoAqlQueryTest extends AbstractArangoRepositoryTest { public void findOneByIdAqlWithNamedParameterTest() { repository.save(customers); final Map retrieved = repository.findOneByIdAqlWithNamedParameter(john.getId(), OPTIONS); - final Customer retrievedCustomer = template.getConverter().read(Customer.class, new DBDocumentEntity(retrieved)); + final Customer retrievedCustomer = template.getConverter().read(Customer.class, + new DBDocumentEntity(retrieved)); assertEquals(john, retrievedCustomer); } @@ -60,7 +61,7 @@ public void findOneByIdAndNameAqlTest() { allProperties.put("_rev", retrieved.getRevision()); retrieved.getProperties().forEach((k, v) -> allProperties.put(k, v)); final Customer retrievedCustomer = template.getConverter().read(Customer.class, - new DBDocumentEntity(allProperties)); + new DBDocumentEntity(allProperties)); assertEquals(john, retrievedCustomer); } @@ -70,7 +71,8 @@ public void findOneByBindVarsAqlTest() { final Map bindVars = new HashMap<>(); bindVars.put("id", john.getId()); bindVars.put("name", john.getName()); - final ArangoCursor retrieved = repository.findOneByBindVarsAql(OPTIONS.ttl(127).cache(true), bindVars); + final ArangoCursor retrieved = repository.findOneByBindVarsAql(OPTIONS.ttl(127).cache(true), + bindVars); assertEquals(john, retrieved.next()); } @@ -107,7 +109,7 @@ public void findOneByIdAndNameWithBindVarsAqlTest() { public void findOneByIdInCollectionAqlWithUnusedParamTest() { repository.save(customers); final Customer retrieved = repository.findOneByIdInCollectionAqlWithUnusedParam(john.getId().split("/")[0], - john.getId(), john.getId()); + john.getId(), john.getId()); assertEquals(john, retrieved); } @@ -115,7 +117,7 @@ public void findOneByIdInCollectionAqlWithUnusedParamTest() { public void findOneByIdInNamedCollectionAqlWithUnusedParamTest() { repository.save(customers); final Customer retrieved = repository.findOneByIdInNamedCollectionAqlWithUnusedParam(john.getId().split("/")[0], - john.getId(), john.getId()); + john.getId(), john.getId()); assertEquals(john, retrieved); } @@ -123,7 +125,7 @@ public void findOneByIdInNamedCollectionAqlWithUnusedParamTest() { public void findOneByIdInIncorrectNamedCollectionAqlTest() { repository.save(customers); final Customer retrieved = repository.findOneByIdInIncorrectNamedCollectionAql(john.getId().split("/")[0], - john.getId(), john.getId()); + john.getId(), john.getId()); assertEquals(john, retrieved); } @@ -131,7 +133,7 @@ public void findOneByIdInIncorrectNamedCollectionAqlTest() { public void findOneByIdInNamedCollectionAqlRejectedTest() { repository.save(customers); final Customer retrieved = repository.findOneByIdInNamedCollectionAqlRejected(john.getId().split("/")[0], - john.getId()); + john.getId()); assertEquals(john, retrieved); } @@ -174,7 +176,7 @@ public void findOneByIdWithStaticProjectionTest() { public void findManyLegalAgeWithStaticProjectionTest() { repository.save(customers); final List retrieved = repository.findManyLegalAgeWithStaticProjection(); - for (CustomerNameProjection proj : retrieved) { + for (final CustomerNameProjection proj : retrieved) { assertThat(proj.getName(), isOneOf(john.getName(), bob.getName())); } } @@ -183,7 +185,7 @@ public void findManyLegalAgeWithStaticProjectionTest() { public void findOneByIdWithDynamicProjectionTest() { repository.save(customers); final CustomerNameProjection retrieved = repository.findOneByIdWithDynamicProjection(john.getId(), - CustomerNameProjection.class); + CustomerNameProjection.class); assertEquals(retrieved.getName(), john.getName()); } @@ -192,7 +194,7 @@ public void findManyLegalAgeWithDynamicProjectionTest() { repository.save(customers); final List retrieved = repository .findManyLegalAgeWithDynamicProjection(CustomerNameProjection.class); - for (CustomerNameProjection proj : retrieved) { + for (final CustomerNameProjection proj : retrieved) { assertThat(proj.getName(), isOneOf(john.getName(), bob.getName())); } } @@ -206,8 +208,8 @@ public void pageableTest() { repository.save(new Customer("B", "B", 3)); toBeRetrieved.add(new Customer("A", "A", 4)); repository.save(new Customer("A", "A", 5)); - repository.saveAll(toBeRetrieved); - final Pageable pageable = PageRequest.of(1, 2, Sort.by("c.age")); + repository.save(toBeRetrieved); + final Pageable pageable = new PageRequest(1, 2, new Sort("c.age")); final Page retrieved = repository.findByNameAndSurnameWithPageable(pageable, "A", "A"); assertThat(retrieved.getTotalElements(), is(5L)); assertThat(retrieved.getTotalPages(), is(3)); @@ -225,7 +227,7 @@ public void sortTest() { repository.save(toBeRetrieved.get(2)); repository.save(new Customer("C", "C", 0)); final List retrieved = repository - .findByNameWithSort(Sort.by(Direction.DESC, "c.surname").and(Sort.by("c.age")), "A"); + .findByNameWithSort(new Sort(Direction.DESC, "c.surname").and(new Sort("c.age")), "A"); assertThat(retrieved, is(toBeRetrieved)); } From 47d5566487908914cd8d010dc8377e618dc9a457 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 4 Jun 2018 14:03:28 +0200 Subject: [PATCH 46/94] prepare release 1.1.3 --- pom.xml | 720 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 360 insertions(+), 360 deletions(-) diff --git a/pom.xml b/pom.xml index 6531391f1..673b7d558 100644 --- a/pom.xml +++ b/pom.xml @@ -1,360 +1,360 @@ - - 4.0.0 - - com.arangodb - arangodb-spring-data - 1.1.3-SNAPSHOT - 2017 - jar - - arangodb-spring-data - ArangoDB Spring Data - http://maven.apache.org - - - - Apache License 2.0 - http://www.apache.org/licenses/LICENSE-2.0 - repo - - - - - UTF-8 - 1.1.3 - 1.3 - 4.12 - 4.4.0 - 4.3.13.RELEASE - ${spring.version} - ${spring.version} - ${spring.version} - ${spring.version} - ${spring.version} - ${spring.version} - 1.13.9.RELEASE - - - - - mpv1989 - Mark Vollmary - https://github.com/mpv1989 - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - arangodb-snapshots - https://oss.sonatype.org/content/groups/staging - - - spring-libs-milestone - https://repo.spring.io/libs-milestone - - - spring-libs-snapshot - https://repo.spring.io/libs-snapshot - - - - - - doclint-java8-disable - - [1.8,) - - - -Xdoclint:none - - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.5 - true - - ossrh - https://oss.sonatype.org/ - 84aff6e87e214c - false - - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.4.1 - - - assembly - package - - single - - - - - - ${project.artifactId}-${project.version}-standalone - - false - false - - jar-with-dependencies - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.2 - - 1.8 - 1.8 - - - - - - org.apache.maven.plugins - maven-resources-plugin - 2.7 - - UTF-8 - - - - - org.apache.maven.plugins - maven-source-plugin - 2.4 - - - - jar - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - - jar - - - ${javadoc.opts} - - - - - - - maven-surefire-plugin - 2.19.1 - - - **/*Test.java - **/*Example.java - - - - - - maven-deploy-plugin - 2.8.2 - - false - 10 - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - - - - - - - com.arangodb - arangodb-java-driver - - - com.arangodb - velocypack-module-jdk8 - - - com.arangodb - velocypack-module-joda - - - - org.springframework - spring-tx - - - org.springframework - spring-context - - - org.springframework - spring-beans - - - org.springframework - spring-core - - - org.springframework - spring-expression - - - - org.springframework.data - spring-data-commons - - - - org.springframework - spring-test - test - - - ch.qos.logback - logback-classic - test - - - junit - junit - test - - - org.hamcrest - hamcrest-all - test - - - - - - - com.arangodb - arangodb-java-driver - ${arangodb-java-driver.version} - - - com.arangodb - velocypack-module-jdk8 - 1.1.0 - - - com.arangodb - velocypack-module-joda - 1.1.1 - - - - org.springframework - spring-tx - ${spring-tx.version} - - - org.springframework - spring-context - ${spring-context.version} - - - org.springframework - spring-beans - ${spring-beans.version} - - - org.springframework - spring-core - ${spring-core.version} - - - org.springframework - spring-expression - ${spring-expression.version} - - - - org.springframework.data - spring-data-commons - ${spring-data-commons.version} - - - - org.springframework - spring-test - ${spring-test.version} - - - ch.qos.logback - logback-classic - ${logback-classic.version} - - - junit - junit - ${junit.version} - - - org.hamcrest - hamcrest-all - ${hamcrest-all.version} - - - - - - https://github.com/arangodb/spring-data - scm:git:git://github.com/arangodb/spring-data.git - scm:git:git://github.com/arangodb/spring-data.git - - - - ArangoDB GmbH - https://www.arangodb.com - - - + + 4.0.0 + + com.arangodb + arangodb-spring-data + 1.1.3 + 2017 + jar + + arangodb-spring-data + ArangoDB Spring Data + http://maven.apache.org + + + + Apache License 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + UTF-8 + 1.1.3 + 1.3 + 4.12 + 4.4.0 + 4.3.13.RELEASE + ${spring.version} + ${spring.version} + ${spring.version} + ${spring.version} + ${spring.version} + ${spring.version} + 1.13.9.RELEASE + + + + + mpv1989 + Mark Vollmary + https://github.com/mpv1989 + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + arangodb-snapshots + https://oss.sonatype.org/content/groups/staging + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + + + + + doclint-java8-disable + + [1.8,) + + + -Xdoclint:none + + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.5 + true + + ossrh + https://oss.sonatype.org/ + 84aff6e87e214c + false + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4.1 + + + assembly + package + + single + + + + + + ${project.artifactId}-${project.version}-standalone + + false + false + + jar-with-dependencies + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.2 + + 1.8 + 1.8 + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.7 + + UTF-8 + + + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + + + jar + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + + jar + + + ${javadoc.opts} + + + + + + + maven-surefire-plugin + 2.19.1 + + + **/*Test.java + **/*Example.java + + + + + + maven-deploy-plugin + 2.8.2 + + false + 10 + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + + + + com.arangodb + arangodb-java-driver + + + com.arangodb + velocypack-module-jdk8 + + + com.arangodb + velocypack-module-joda + + + + org.springframework + spring-tx + + + org.springframework + spring-context + + + org.springframework + spring-beans + + + org.springframework + spring-core + + + org.springframework + spring-expression + + + + org.springframework.data + spring-data-commons + + + + org.springframework + spring-test + test + + + ch.qos.logback + logback-classic + test + + + junit + junit + test + + + org.hamcrest + hamcrest-all + test + + + + + + + com.arangodb + arangodb-java-driver + ${arangodb-java-driver.version} + + + com.arangodb + velocypack-module-jdk8 + 1.1.0 + + + com.arangodb + velocypack-module-joda + 1.1.1 + + + + org.springframework + spring-tx + ${spring-tx.version} + + + org.springframework + spring-context + ${spring-context.version} + + + org.springframework + spring-beans + ${spring-beans.version} + + + org.springframework + spring-core + ${spring-core.version} + + + org.springframework + spring-expression + ${spring-expression.version} + + + + org.springframework.data + spring-data-commons + ${spring-data-commons.version} + + + + org.springframework + spring-test + ${spring-test.version} + + + ch.qos.logback + logback-classic + ${logback-classic.version} + + + junit + junit + ${junit.version} + + + org.hamcrest + hamcrest-all + ${hamcrest-all.version} + + + + + + https://github.com/arangodb/spring-data + scm:git:git://github.com/arangodb/spring-data.git + scm:git:git://github.com/arangodb/spring-data.git + + + + ArangoDB GmbH + https://www.arangodb.com + + + From 0c0ef0f95d6effae185588aa9c025fdda9b2679f Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 4 Jun 2018 14:11:33 +0200 Subject: [PATCH 47/94] prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 673b7d558..5ac21aa07 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.3 + 1.1.4-SNAPSHOT 2017 jar From 42316d7ac79ec600431ce80f4ae2230657a2b118 Mon Sep 17 00:00:00 2001 From: Christian Lechner <6638938+christian-lechner@users.noreply.github.com> Date: Thu, 7 Jun 2018 14:18:12 +0200 Subject: [PATCH 48/94] fix issue #43 (#57) --- .../core/convert/resolver/RelationsResolver.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java index 35877c360..38f7e751f 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java @@ -21,8 +21,8 @@ package com.arangodb.springframework.core.convert.resolver; import java.util.Arrays; -import java.util.Collection; +import com.arangodb.ArangoCursor; import com.arangodb.model.AqlQueryOptions; import com.arangodb.springframework.annotation.Relations; import com.arangodb.springframework.core.ArangoOperations; @@ -30,6 +30,7 @@ /** * @author Mark Vollmary + * @author Christian Lechner * */ public class RelationsResolver extends AbstractResolver @@ -49,13 +50,13 @@ public Object resolveOne(final String id, final Class type, final Relations a @Override public Object resolveMultiple(final String id, final Class type, final Relations annotation) { - return annotation.lazy() ? proxy(id, Collection.class, annotation, (i, t, a) -> resolve(i, type, a)) + return annotation.lazy() ? proxy(id, type, annotation, (i, t, a) -> resolve(i, type, a)) : resolve(id, type, annotation); } @Override public Object resolve(final String id, final Class type, final Relations annotation) { - return template.query( + final ArangoCursor result = template.query( "WITH @@vertex FOR v IN " + Math.max(1, annotation.minDepth()) + ".." + Math.max(1, annotation.maxDepth()) + " " + annotation.direction() + " @start @@edges OPTIONS {bfs: true, uniqueVertices: \"global\"} RETURN v", @@ -64,7 +65,9 @@ public Object resolve(final String id, final Class type, final Relations anno Arrays.asList(annotation.edges()).stream().map((e) -> template.collection(e).name()) .reduce((a, b) -> a + ", " + b).get()) .put("@vertex", type).get(), - new AqlQueryOptions(), type).asListRemaining(); + new AqlQueryOptions(), type); + + return result.hasNext() ? result.next() : null; } } From 852c261b3a21f8a002ff98c63c8f4715a099aa98 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 7 Jun 2018 14:51:22 +0200 Subject: [PATCH 49/94] prepare release 1.1.4 --- ChangeLog | 5 +++++ pom.xml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index f27fdd3d5..7722a2a78 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +v1.1.4 (2018-06-07) +--------------------------- +* upgraded arangodb-java-driver to 4.4.1 +* fixed relation cycle (issue #43) + v1.1.3 (2018-06-04) --------------------------- * fixed support for ArangoCusor as query-method return type diff --git a/pom.xml b/pom.xml index 5ac21aa07..73720363d 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 1.1.3 1.3 4.12 - 4.4.0 + 4.4.1 4.3.13.RELEASE ${spring.version} ${spring.version} From 49e395891eac936f3b83313ac4e49c895a398847 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 7 Jun 2018 14:52:03 +0200 Subject: [PATCH 50/94] prepare release 1.1.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 73720363d..60bef1875 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.4-SNAPSHOT + 1.1.4 2017 jar From e08dba7941ad6f7f538d760c865d7cda1a5e60ab Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 7 Jun 2018 16:06:22 +0200 Subject: [PATCH 51/94] Fix issue 43 (#58) --- .../core/convert/DefaultArangoConverter.java | 1105 +++++++++-------- .../core/convert/resolver/FromResolver.java | 135 +- .../convert/resolver/RelationResolver.java | 70 +- .../convert/resolver/RelationsResolver.java | 147 +-- .../core/convert/resolver/ToResolver.java | 135 +- 5 files changed, 798 insertions(+), 794 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java index aea39a400..ab0c88e94 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java @@ -1,552 +1,553 @@ -/* - * 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.core.convert; - -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.springframework.core.CollectionFactory; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.data.convert.EntityInstantiator; -import org.springframework.data.convert.EntityInstantiators; -import org.springframework.data.mapping.Association; -import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.mapping.model.ConvertingPropertyAccessor; -import org.springframework.data.mapping.model.MappingException; -import org.springframework.data.mapping.model.ParameterValueProvider; -import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; -import org.springframework.data.mapping.model.PropertyValueProvider; -import org.springframework.data.util.ClassTypeInformation; -import org.springframework.data.util.TypeInformation; -import org.springframework.util.ClassUtils; -import org.springframework.util.CollectionUtils; - -import com.arangodb.springframework.core.convert.resolver.ResolverFactory; -import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; -import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; -import com.arangodb.springframework.core.mapping.ArangoSimpleTypes; - -/** - * @author Mark Vollmary - * @author Christian Lechner - * - */ -public class DefaultArangoConverter implements ArangoConverter { - - private static final String _ID = "_id"; - private static final String _KEY = "_key"; - private final MappingContext, ArangoPersistentProperty> context; - private final CustomConversions conversions; - private final GenericConversionService conversionService; - private final EntityInstantiators instantiators; - private final ResolverFactory resolverFactory; - private final ArangoTypeMapper typeMapper; - - public DefaultArangoConverter( - final MappingContext, ArangoPersistentProperty> context, - final CustomConversions conversions, final ResolverFactory resolverFactory, final ArangoTypeMapper typeMapper) { - super(); - this.context = context; - this.conversions = conversions; - this.resolverFactory = resolverFactory; - this.typeMapper = typeMapper; - conversionService = new DefaultConversionService(); - conversions.registerConvertersIn(conversionService); - instantiators = new EntityInstantiators(); - } - - @Override - public MappingContext, ArangoPersistentProperty> getMappingContext() { - return context; - } - - @Override - public ArangoTypeMapper getTypeMapper() { - return typeMapper; - } - - @SuppressWarnings("unchecked") - @Override - public R read(final Class type, final DBEntity source) { - return (R) read(ClassTypeInformation.from(type), source); - } - - private Object read(final TypeInformation type, final DBEntity source) { - if (source == null) { - return null; - } - - final TypeInformation typeToUse = typeMapper.readType(source, type); - - if (conversions.hasCustomReadTarget(source.getClass(), typeToUse.getType())) { - return conversionService.convert(source, typeToUse.getType()); - } - - if (DBEntity.class.isAssignableFrom(typeToUse.getType())) { - return source; - } - - if (typeToUse.isMap()) { - return readMap(typeToUse, DBDocumentEntity.class.cast(source)); - } - if (typeToUse.isCollectionLike()) { - return readCollection(typeToUse, DBCollectionEntity.class.cast(source)); - } - - // no type information available => stick to the given type of the source - if (typeToUse.equals(ClassTypeInformation.OBJECT)) { - if (source instanceof DBDocumentEntity) { - return readMap(ClassTypeInformation.MAP, DBDocumentEntity.class.cast(source)); - } else if (source instanceof DBCollectionEntity) { - return readCollection(ClassTypeInformation.LIST, DBCollectionEntity.class.cast(source)); - } - return source; - } - - final ArangoPersistentEntity entity = context.getPersistentEntity(typeToUse.getType()); - return read(typeToUse, source, entity); - } - - private Object readMap(final TypeInformation type, final DBDocumentEntity source) { - final Class keyType = getNonNullComponentType(type).getType(); - final TypeInformation valueType = getNonNullMapValueType(type); - final Map map = CollectionFactory.createMap(type.getType(), keyType, source.size()); - for (final Map.Entry entry : source.entrySet()) { - if (typeMapper.isTypeKey(entry.getKey())) { - continue; - } - final Object key = convertIfNecessary(entry.getKey(), keyType); - final Object value = entry.getValue(); - if (value instanceof DBEntity) { - map.put(key, read(valueType, (DBEntity) value)); - } else { - map.put(key, convertIfNecessary(value, valueType.getType())); - } - } - return map; - } - - private Object readCollection(final TypeInformation type, final DBCollectionEntity source) { - final Class collectionType = Collection.class.isAssignableFrom(type.getType()) ? type.getType() : List.class; - final TypeInformation componentType = getNonNullComponentType(type); - final Collection entries = type.getType().isArray() ? new ArrayList<>() - : CollectionFactory.createCollection(collectionType, componentType.getType(), source.size()); - for (final Object entry : source) { - if (entry instanceof DBEntity) { - entries.add(read(componentType, (DBEntity) entry)); - } else { - entries.add(convertIfNecessary(entry, componentType.getType())); - } - } - return entries; - } - - private Object read(final TypeInformation type, final DBEntity source, final ArangoPersistentEntity entity) { - final EntityInstantiator instantiatorFor = instantiators.getInstantiatorFor(entity); - final ParameterValueProvider provider = getParameterProvider(entity, source); - final Object instance = instantiatorFor.createInstance(entity, provider); - final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(instance), - conversionService); - - entity.doWithProperties((final ArangoPersistentProperty property) -> { - if (!entity.isConstructorArgument(property)) { - readProperty(source.get(_ID), accessor, source.get(property.getFieldName()), property); - } - }); - entity.doWithAssociations((final Association association) -> { - final ArangoPersistentProperty property = association.getInverse(); - if (!entity.isConstructorArgument(property)) { - readProperty(source.get(_ID), accessor, source.get(property.getFieldName()), property); - } - }); - return instance; - } - - private ParameterValueProvider getParameterProvider( - final ArangoPersistentEntity entity, - final DBEntity source) { - final PropertyValueProvider provider = new ArangoPropertyValueProvider(source); - return new PersistentEntityParameterValueProvider<>(entity, provider, null); - } - - private class ArangoPropertyValueProvider implements PropertyValueProvider { - private final DBEntity source; - - public ArangoPropertyValueProvider(final DBEntity source) { - super(); - this.source = source; - } - - @SuppressWarnings("unchecked") - @Override - public T getPropertyValue(final ArangoPersistentProperty property) { - final Optional referenceOrRelation = readReferenceOrRelation(source.get(_ID), - source.get(property.getFieldName()), property); - return (T) referenceOrRelation.orElseGet(() -> convertIfNecessary( - read(source.get(property.getFieldName()), property.getTypeInformation()), property.getType())); - } - - } - - private void readProperty( - final Object parentId, - final ConvertingPropertyAccessor accessor, - final Object source, - final ArangoPersistentProperty property) { - final Optional referenceOrRelation = readReferenceOrRelation(parentId, source, property); - accessor.setProperty(property, - referenceOrRelation.orElseGet(() -> read(source, property.getTypeInformation()))); - } - - private Optional readReferenceOrRelation( - final Object parentId, - final Object source, - final ArangoPersistentProperty property) { - Optional tmp = Optional.empty(); - if (source != null) { - if (!tmp.isPresent()) { - final Optional ref = property.getRef() - .flatMap(annotation -> readReference(source, property, annotation)); - if (ref.isPresent()) { - tmp = ref; - } - } - } - for (final Optional annotation : Arrays.asList(property.getRelations(), - property.getFrom(), property.getTo())) { - final Optional relation = annotation.flatMap(a -> readRelation(parentId, source, property, a)); - if (relation.isPresent()) { - tmp = relation; - break; - } - } - return tmp; - } - - @SuppressWarnings("unchecked") - private Optional readReference( - final Object source, - final ArangoPersistentProperty property, - final Annotation annotation) { - return resolverFactory.getReferenceResolver(annotation).flatMap(resolver -> { - if (property.isCollectionLike()) { - final Collection ids; - try { - ids = (Collection) asCollection(source); - } catch (final Exception e) { - throw new MappingException( - "Collection of Type String expected for references but found type " + source.getClass()); - } - return Optional.ofNullable(resolver.resolveMultiple(ids, - getNonNullComponentType(property.getTypeInformation()).getType(), annotation)); - } else { - if (!(source instanceof String)) { - throw new MappingException( - "Type String expected for reference but found type " + source.getClass()); - } - return Optional.ofNullable( - resolver.resolveOne(source.toString(), property.getTypeInformation().getType(), annotation)); - } - }); - } - - private Optional readRelation( - final Object parentId, - final Object source, - final ArangoPersistentProperty property, - final A annotation) { - return resolverFactory.getRelationResolver(annotation).flatMap(resolver -> { - if (property.isCollectionLike() && parentId != null) { - return Optional.of(resolver.resolveMultiple(parentId.toString(), - getNonNullComponentType(property.getTypeInformation()).getType(), annotation)); - } else if (source != null) { - return Optional.of( - resolver.resolveOne(source.toString(), property.getTypeInformation().getType(), annotation)); - } - return Optional.empty(); - }); - } - - @SuppressWarnings("unchecked") - private T read(final Object source, final TypeInformation type) { - if (source == null) { - return null; - } - if (conversions.hasCustomReadTarget(source.getClass(), type.getType())) { - return (T) conversionService.convert(source, type.getType()); - } - if (source instanceof DBEntity) { - return (T) read(type, DBEntity.class.cast(source)); - } - return (T) source; - } - - @Override - public void write(final Object source, final DBEntity sink) { - if (source == null) { - return; - } - - if (sink instanceof DBDocumentEntity - && conversions.hasCustomWriteTarget(source.getClass(), DBDocumentEntity.class)) { - final DBDocumentEntity result = conversionService.convert(source, DBDocumentEntity.class); - ((DBDocumentEntity) sink).putAll(result); - } - - final TypeInformation type = ClassTypeInformation.from(ClassUtils.getUserClass(source.getClass())); - final TypeInformation definedType = ClassTypeInformation.OBJECT; - - write(source, type, sink, definedType); - } - - @SuppressWarnings("unchecked") - private void write( - final Object source, - final TypeInformation type, - final DBEntity sink, - final TypeInformation definedType) { - - if (type.isMap()) { - writeMap((Map) source, sink, definedType); - return; - } - if (type.isCollectionLike()) { - writeCollection(source, sink, definedType); - return; - } - write(source, sink, context.getPersistentEntity(type)); - addTypeKeyIfNecessary(definedType, source, sink); - } - - private void write(final Object source, final DBEntity sink, final ArangoPersistentEntity entity) { - - final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source); - - entity.doWithProperties((final ArangoPersistentProperty property) -> { - if (!property.isWritable()) { - return; - } - final Object propertyObj = accessor.getProperty(property); - if (propertyObj != null) { - writeProperty(propertyObj, sink, property); - } - }); - entity.doWithAssociations((final Association association) -> { - final ArangoPersistentProperty inverse = association.getInverse(); - final Object property = accessor.getProperty(inverse); - if (property != null) { - writeProperty(property, sink, inverse); - } - }); - final Object id = sink.get(_ID); - if (id != null && sink.get(_KEY) == null) { - sink.put(_KEY, determineDocumentKeyFromId(id.toString())); - } - } - - @SuppressWarnings("unchecked") - private void writeProperty(final Object source, final DBEntity sink, final ArangoPersistentProperty property) { - if (source == null) { - return; - } - final String fieldName = property.getFieldName(); - final TypeInformation valueType = ClassTypeInformation.from(source.getClass()); - if (property.getRef().isPresent()) { - if (valueType.isCollectionLike()) { - final Collection ids = new ArrayList<>(); - for (final Object ref : createCollection(asCollection(source), property)) { - getId(ref).ifPresent(id -> ids.add(id)); - } - sink.put(fieldName, ids); - } else { - getId(source).ifPresent(id -> sink.put(fieldName, id)); - } - return; - } - if (property.getRelations().isPresent()) { - return; - } - if (property.getFrom().isPresent() || property.getTo().isPresent()) { - if (!valueType.isCollectionLike()) { - getId(source).ifPresent(id -> sink.put(fieldName, id)); - } - return; - } - if (conversions.isSimpleType(valueType.getType())) { - final Optional> customWriteTarget = Optional - .ofNullable(conversions.getCustomWriteTarget(source.getClass())); - final Class targetType = customWriteTarget.orElseGet(() -> valueType.getType()); - sink.put(fieldName, conversionService.convert(source, targetType)); - return; - } - if (valueType.isCollectionLike()) { - final DBEntity collection = new DBCollectionEntity(); - writeCollection(source, collection, property.getTypeInformation()); - sink.put(fieldName, collection); - return; - } - if (valueType.isMap()) { - final DBEntity map = new DBDocumentEntity(); - writeMap((Map) source, map, property.getTypeInformation()); - sink.put(fieldName, map); - return; - } - final ArangoPersistentEntity persistentEntity = context.getPersistentEntity(valueType); - final DBEntity document = new DBDocumentEntity(); - write(source, document, persistentEntity); - addTypeKeyIfNecessary(property.getTypeInformation(), source, document); - sink.put(fieldName, document); - return; - } - - private void writeMap(final Map source, final DBEntity sink, final TypeInformation definedType) { - for (final Entry entry : source.entrySet()) { - final Object key = entry.getKey(); - if (!conversions.isSimpleType(key.getClass()) || key instanceof DBEntity) { - throw new MappingException( - "Complex type " + key.getClass().getName() + " is not allowed as a map key!"); - } - final Object value = entry.getValue(); - final Class valueType = value.getClass(); - if (conversions.isSimpleType(valueType)) { - final Optional> customWriteTarget = Optional - .ofNullable(conversions.getCustomWriteTarget(valueType)); - final Class targetType = customWriteTarget.orElseGet(() -> valueType); - sink.put(convertMapKey(key), conversionService.convert(value, targetType)); - } else { - final DBEntity entity = createDBEntity(valueType); - write(value, ClassTypeInformation.from(valueType), entity, getNonNullMapValueType(definedType)); - sink.put(convertMapKey(key), entity); - } - } - } - - private void writeCollection(final Object source, final DBEntity sink, final TypeInformation definedType) { - for (final Object entry : asCollection(source)) { - final Class valueType = entry.getClass(); - if (conversions.isSimpleType(valueType)) { - final Optional> customWriteTarget = Optional - .ofNullable(conversions.getCustomWriteTarget(valueType)); - final Class targetType = customWriteTarget.orElseGet(() -> valueType); - sink.add(conversionService.convert(entry, targetType)); - } else { - final DBEntity entity = createDBEntity(valueType); - write(entry, ClassTypeInformation.from(valueType), entity, getNonNullComponentType(definedType)); - sink.add(entity); - } - } - } - - private Optional getId(final Object source) { - return getId(source, context.getPersistentEntity(source.getClass())); - } - - private Optional getId(final Object source, final ArangoPersistentEntity entity) { - return Optional.ofNullable(entity.getIdentifierAccessor(source).getIdentifier()); - } - - private Collection createCollection(final Collection source, final ArangoPersistentProperty property) { - return source.stream() - .map( - s -> conversionService.convert(s, getNonNullComponentType(property.getTypeInformation()).getType())) - .collect(Collectors.toList()); - } - - private static Collection asCollection(final Object source) { - return (source instanceof Collection) ? Collection.class.cast(source) - : source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source); - } - - private DBEntity createDBEntity(final Class type) { - return isCollectionType(type) ? new DBCollectionEntity() : new DBDocumentEntity(); - } - - private boolean isArangoSimpleType(final Class type) { - return ArangoSimpleTypes.HOLDER.isSimpleType(type); - } - - @Override - public boolean isCollectionType(final Class type) { - return type.isArray() || Iterable.class.equals(type) || Collection.class.isAssignableFrom(type); - } - - private boolean isMapType(final Class type) { - return Map.class.isAssignableFrom(type); - } - - @Override - public GenericConversionService getConversionService() { - return conversionService; - } - - @Override - public boolean isEntityType(final Class type) { - return !isArangoSimpleType(type) && !isMapType(type) && !isCollectionType(type); - } - - @SuppressWarnings("unchecked") - private T convertIfNecessary(final Object source, final Class type) { - return (T) (source == null ? source - : type.isAssignableFrom(source.getClass()) ? source : conversionService.convert(source, type)); - } - - private String determineDocumentKeyFromId(final String id) { - final String[] split = id.split("/"); - return split[split.length - 1]; - } - - private void addTypeKeyIfNecessary(final TypeInformation definedType, final Object value, final DBEntity sink) { - final Class referenceType = definedType != null ? definedType.getType() : Object.class; - final Class valueType = ClassUtils.getUserClass(value.getClass()); - if (!valueType.equals(referenceType)) { - typeMapper.writeType(valueType, sink); - } - } - - private String convertMapKey(final Object key) { - if (key instanceof String) { - return (String) key; - } - final boolean hasCustomConverter = conversions.hasCustomWriteTarget(key.getClass(), String.class); - return hasCustomConverter ? conversionService.convert(key, String.class) : key.toString(); - } - - private TypeInformation getNonNullComponentType(final TypeInformation type) { - final TypeInformation compType = type.getComponentType(); - return compType != null ? compType : ClassTypeInformation.OBJECT; - } - - private TypeInformation getNonNullMapValueType(final TypeInformation type) { - final TypeInformation valueType = type.getMapValueType(); - return valueType != null ? valueType : ClassTypeInformation.OBJECT; - } - -} +/* + * 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.core.convert; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.core.CollectionFactory; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.convert.support.GenericConversionService; +import org.springframework.data.convert.EntityInstantiator; +import org.springframework.data.convert.EntityInstantiators; +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.ConvertingPropertyAccessor; +import org.springframework.data.mapping.model.MappingException; +import org.springframework.data.mapping.model.ParameterValueProvider; +import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider; +import org.springframework.data.mapping.model.PropertyValueProvider; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; + +import com.arangodb.springframework.core.convert.resolver.ResolverFactory; +import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; +import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; +import com.arangodb.springframework.core.mapping.ArangoSimpleTypes; + +/** + * @author Mark Vollmary + * @author Christian Lechner + * + */ +public class DefaultArangoConverter implements ArangoConverter { + + private static final String _ID = "_id"; + private static final String _KEY = "_key"; + private final MappingContext, ArangoPersistentProperty> context; + private final CustomConversions conversions; + private final GenericConversionService conversionService; + private final EntityInstantiators instantiators; + private final ResolverFactory resolverFactory; + private final ArangoTypeMapper typeMapper; + + public DefaultArangoConverter( + final MappingContext, ArangoPersistentProperty> context, + final CustomConversions conversions, final ResolverFactory resolverFactory, final ArangoTypeMapper typeMapper) { + super(); + this.context = context; + this.conversions = conversions; + this.resolverFactory = resolverFactory; + this.typeMapper = typeMapper; + conversionService = new DefaultConversionService(); + conversions.registerConvertersIn(conversionService); + instantiators = new EntityInstantiators(); + } + + @Override + public MappingContext, ArangoPersistentProperty> getMappingContext() { + return context; + } + + @Override + public ArangoTypeMapper getTypeMapper() { + return typeMapper; + } + + @SuppressWarnings("unchecked") + @Override + public R read(final Class type, final DBEntity source) { + return (R) read(ClassTypeInformation.from(type), source); + } + + private Object read(final TypeInformation type, final DBEntity source) { + if (source == null) { + return null; + } + + final TypeInformation typeToUse = typeMapper.readType(source, type); + + if (conversions.hasCustomReadTarget(source.getClass(), typeToUse.getType())) { + return conversionService.convert(source, typeToUse.getType()); + } + + if (DBEntity.class.isAssignableFrom(typeToUse.getType())) { + return source; + } + + if (typeToUse.isMap()) { + return readMap(typeToUse, DBDocumentEntity.class.cast(source)); + } + if (typeToUse.isCollectionLike()) { + return readCollection(typeToUse, DBCollectionEntity.class.cast(source)); + } + + // no type information available => stick to the given type of the source + if (typeToUse.equals(ClassTypeInformation.OBJECT)) { + if (source instanceof DBDocumentEntity) { + return readMap(ClassTypeInformation.MAP, DBDocumentEntity.class.cast(source)); + } else if (source instanceof DBCollectionEntity) { + return readCollection(ClassTypeInformation.LIST, DBCollectionEntity.class.cast(source)); + } + return source; + } + + final ArangoPersistentEntity entity = context.getPersistentEntity(typeToUse.getType()); + return read(typeToUse, source, entity); + } + + private Object readMap(final TypeInformation type, final DBDocumentEntity source) { + final Class keyType = getNonNullComponentType(type).getType(); + final TypeInformation valueType = getNonNullMapValueType(type); + final Map map = CollectionFactory.createMap(type.getType(), keyType, source.size()); + for (final Map.Entry entry : source.entrySet()) { + if (typeMapper.isTypeKey(entry.getKey())) { + continue; + } + final Object key = convertIfNecessary(entry.getKey(), keyType); + final Object value = entry.getValue(); + if (value instanceof DBEntity) { + map.put(key, read(valueType, (DBEntity) value)); + } else { + map.put(key, convertIfNecessary(value, valueType.getType())); + } + } + return map; + } + + private Object readCollection(final TypeInformation type, final DBCollectionEntity source) { + final Class collectionType = Collection.class.isAssignableFrom(type.getType()) ? type.getType() : List.class; + final TypeInformation componentType = getNonNullComponentType(type); + final Collection entries = type.getType().isArray() ? new ArrayList<>() + : CollectionFactory.createCollection(collectionType, componentType.getType(), source.size()); + for (final Object entry : source) { + if (entry instanceof DBEntity) { + entries.add(read(componentType, (DBEntity) entry)); + } else { + entries.add(convertIfNecessary(entry, componentType.getType())); + } + } + return entries; + } + + private Object read(final TypeInformation type, final DBEntity source, final ArangoPersistentEntity entity) { + final EntityInstantiator instantiatorFor = instantiators.getInstantiatorFor(entity); + final ParameterValueProvider provider = getParameterProvider(entity, source); + final Object instance = instantiatorFor.createInstance(entity, provider); + final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(instance), + conversionService); + + entity.doWithProperties((final ArangoPersistentProperty property) -> { + if (!entity.isConstructorArgument(property)) { + readProperty(source.get(_ID), accessor, source.get(property.getFieldName()), property); + } + }); + entity.doWithAssociations((final Association association) -> { + final ArangoPersistentProperty property = association.getInverse(); + if (!entity.isConstructorArgument(property)) { + readProperty(source.get(_ID), accessor, source.get(property.getFieldName()), property); + } + }); + return instance; + } + + private ParameterValueProvider getParameterProvider( + final ArangoPersistentEntity entity, + final DBEntity source) { + final PropertyValueProvider provider = new ArangoPropertyValueProvider(source); + return new PersistentEntityParameterValueProvider<>(entity, provider, null); + } + + private class ArangoPropertyValueProvider implements PropertyValueProvider { + private final DBEntity source; + + public ArangoPropertyValueProvider(final DBEntity source) { + super(); + this.source = source; + } + + @SuppressWarnings("unchecked") + @Override + public T getPropertyValue(final ArangoPersistentProperty property) { + final Optional referenceOrRelation = readReferenceOrRelation(source.get(_ID), + source.get(property.getFieldName()), property); + return (T) referenceOrRelation.orElseGet(() -> convertIfNecessary( + read(source.get(property.getFieldName()), property.getTypeInformation()), property.getType())); + } + + } + + private void readProperty( + final Object parentId, + final ConvertingPropertyAccessor accessor, + final Object source, + final ArangoPersistentProperty property) { + final Optional referenceOrRelation = readReferenceOrRelation(parentId, source, property); + accessor.setProperty(property, + referenceOrRelation.orElseGet(() -> read(source, property.getTypeInformation()))); + } + + private Optional readReferenceOrRelation( + final Object parentId, + final Object source, + final ArangoPersistentProperty property) { + Optional tmp = Optional.empty(); + if (source != null) { + if (!tmp.isPresent()) { + final Optional ref = property.getRef() + .flatMap(annotation -> readReference(source, property, annotation)); + if (ref.isPresent()) { + tmp = ref; + } + } + } + for (final Optional annotation : Arrays.asList(property.getRelations(), + property.getFrom(), property.getTo())) { + final Optional relation = annotation.flatMap(a -> readRelation(parentId, source, property, a)); + if (relation.isPresent()) { + tmp = relation; + break; + } + } + return tmp; + } + + @SuppressWarnings("unchecked") + private Optional readReference( + final Object source, + final ArangoPersistentProperty property, + final Annotation annotation) { + return resolverFactory.getReferenceResolver(annotation).flatMap(resolver -> { + if (property.isCollectionLike()) { + final Collection ids; + try { + ids = (Collection) asCollection(source); + } catch (final Exception e) { + throw new MappingException( + "Collection of Type String expected for references but found type " + source.getClass()); + } + return Optional.ofNullable(resolver.resolveMultiple(ids, + getNonNullComponentType(property.getTypeInformation()).getType(), annotation)); + } else { + if (!(source instanceof String)) { + throw new MappingException( + "Type String expected for reference but found type " + source.getClass()); + } + return Optional.ofNullable( + resolver.resolveOne(source.toString(), property.getTypeInformation().getType(), annotation)); + } + }); + } + + private Optional readRelation( + final Object parentId, + final Object source, + final ArangoPersistentProperty property, + final A annotation) { + return resolverFactory.getRelationResolver(annotation).flatMap(resolver -> { + if (property.isCollectionLike() && parentId != null) { + return Optional.of(resolver.resolveMultiple(parentId.toString(), + getNonNullComponentType(property.getTypeInformation()).getType(), + property.getTypeInformation().getType(), annotation)); + } else if (source != null) { + return Optional.of( + resolver.resolveOne(source.toString(), property.getTypeInformation().getType(), annotation)); + } + return Optional.empty(); + }); + } + + @SuppressWarnings("unchecked") + private T read(final Object source, final TypeInformation type) { + if (source == null) { + return null; + } + if (conversions.hasCustomReadTarget(source.getClass(), type.getType())) { + return (T) conversionService.convert(source, type.getType()); + } + if (source instanceof DBEntity) { + return (T) read(type, DBEntity.class.cast(source)); + } + return (T) source; + } + + @Override + public void write(final Object source, final DBEntity sink) { + if (source == null) { + return; + } + + if (sink instanceof DBDocumentEntity + && conversions.hasCustomWriteTarget(source.getClass(), DBDocumentEntity.class)) { + final DBDocumentEntity result = conversionService.convert(source, DBDocumentEntity.class); + ((DBDocumentEntity) sink).putAll(result); + } + + final TypeInformation type = ClassTypeInformation.from(ClassUtils.getUserClass(source.getClass())); + final TypeInformation definedType = ClassTypeInformation.OBJECT; + + write(source, type, sink, definedType); + } + + @SuppressWarnings("unchecked") + private void write( + final Object source, + final TypeInformation type, + final DBEntity sink, + final TypeInformation definedType) { + + if (type.isMap()) { + writeMap((Map) source, sink, definedType); + return; + } + if (type.isCollectionLike()) { + writeCollection(source, sink, definedType); + return; + } + write(source, sink, context.getPersistentEntity(type)); + addTypeKeyIfNecessary(definedType, source, sink); + } + + private void write(final Object source, final DBEntity sink, final ArangoPersistentEntity entity) { + + final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(source); + + entity.doWithProperties((final ArangoPersistentProperty property) -> { + if (!property.isWritable()) { + return; + } + final Object propertyObj = accessor.getProperty(property); + if (propertyObj != null) { + writeProperty(propertyObj, sink, property); + } + }); + entity.doWithAssociations((final Association association) -> { + final ArangoPersistentProperty inverse = association.getInverse(); + final Object property = accessor.getProperty(inverse); + if (property != null) { + writeProperty(property, sink, inverse); + } + }); + final Object id = sink.get(_ID); + if (id != null && sink.get(_KEY) == null) { + sink.put(_KEY, determineDocumentKeyFromId(id.toString())); + } + } + + @SuppressWarnings("unchecked") + private void writeProperty(final Object source, final DBEntity sink, final ArangoPersistentProperty property) { + if (source == null) { + return; + } + final String fieldName = property.getFieldName(); + final TypeInformation valueType = ClassTypeInformation.from(source.getClass()); + if (property.getRef().isPresent()) { + if (valueType.isCollectionLike()) { + final Collection ids = new ArrayList<>(); + for (final Object ref : createCollection(asCollection(source), property)) { + getId(ref).ifPresent(id -> ids.add(id)); + } + sink.put(fieldName, ids); + } else { + getId(source).ifPresent(id -> sink.put(fieldName, id)); + } + return; + } + if (property.getRelations().isPresent()) { + return; + } + if (property.getFrom().isPresent() || property.getTo().isPresent()) { + if (!valueType.isCollectionLike()) { + getId(source).ifPresent(id -> sink.put(fieldName, id)); + } + return; + } + if (conversions.isSimpleType(valueType.getType())) { + final Optional> customWriteTarget = Optional + .ofNullable(conversions.getCustomWriteTarget(source.getClass())); + final Class targetType = customWriteTarget.orElseGet(() -> valueType.getType()); + sink.put(fieldName, conversionService.convert(source, targetType)); + return; + } + if (valueType.isCollectionLike()) { + final DBEntity collection = new DBCollectionEntity(); + writeCollection(source, collection, property.getTypeInformation()); + sink.put(fieldName, collection); + return; + } + if (valueType.isMap()) { + final DBEntity map = new DBDocumentEntity(); + writeMap((Map) source, map, property.getTypeInformation()); + sink.put(fieldName, map); + return; + } + final ArangoPersistentEntity persistentEntity = context.getPersistentEntity(valueType); + final DBEntity document = new DBDocumentEntity(); + write(source, document, persistentEntity); + addTypeKeyIfNecessary(property.getTypeInformation(), source, document); + sink.put(fieldName, document); + return; + } + + private void writeMap(final Map source, final DBEntity sink, final TypeInformation definedType) { + for (final Entry entry : source.entrySet()) { + final Object key = entry.getKey(); + if (!conversions.isSimpleType(key.getClass()) || key instanceof DBEntity) { + throw new MappingException( + "Complex type " + key.getClass().getName() + " is not allowed as a map key!"); + } + final Object value = entry.getValue(); + final Class valueType = value.getClass(); + if (conversions.isSimpleType(valueType)) { + final Optional> customWriteTarget = Optional + .ofNullable(conversions.getCustomWriteTarget(valueType)); + final Class targetType = customWriteTarget.orElseGet(() -> valueType); + sink.put(convertMapKey(key), conversionService.convert(value, targetType)); + } else { + final DBEntity entity = createDBEntity(valueType); + write(value, ClassTypeInformation.from(valueType), entity, getNonNullMapValueType(definedType)); + sink.put(convertMapKey(key), entity); + } + } + } + + private void writeCollection(final Object source, final DBEntity sink, final TypeInformation definedType) { + for (final Object entry : asCollection(source)) { + final Class valueType = entry.getClass(); + if (conversions.isSimpleType(valueType)) { + final Optional> customWriteTarget = Optional + .ofNullable(conversions.getCustomWriteTarget(valueType)); + final Class targetType = customWriteTarget.orElseGet(() -> valueType); + sink.add(conversionService.convert(entry, targetType)); + } else { + final DBEntity entity = createDBEntity(valueType); + write(entry, ClassTypeInformation.from(valueType), entity, getNonNullComponentType(definedType)); + sink.add(entity); + } + } + } + + private Optional getId(final Object source) { + return getId(source, context.getPersistentEntity(source.getClass())); + } + + private Optional getId(final Object source, final ArangoPersistentEntity entity) { + return Optional.ofNullable(entity.getIdentifierAccessor(source).getIdentifier()); + } + + private Collection createCollection(final Collection source, final ArangoPersistentProperty property) { + return source.stream() + .map( + s -> conversionService.convert(s, getNonNullComponentType(property.getTypeInformation()).getType())) + .collect(Collectors.toList()); + } + + private static Collection asCollection(final Object source) { + return (source instanceof Collection) ? Collection.class.cast(source) + : source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source); + } + + private DBEntity createDBEntity(final Class type) { + return isCollectionType(type) ? new DBCollectionEntity() : new DBDocumentEntity(); + } + + private boolean isArangoSimpleType(final Class type) { + return ArangoSimpleTypes.HOLDER.isSimpleType(type); + } + + @Override + public boolean isCollectionType(final Class type) { + return type.isArray() || Iterable.class.equals(type) || Collection.class.isAssignableFrom(type); + } + + private boolean isMapType(final Class type) { + return Map.class.isAssignableFrom(type); + } + + @Override + public GenericConversionService getConversionService() { + return conversionService; + } + + @Override + public boolean isEntityType(final Class type) { + return !isArangoSimpleType(type) && !isMapType(type) && !isCollectionType(type); + } + + @SuppressWarnings("unchecked") + private T convertIfNecessary(final Object source, final Class type) { + return (T) (source == null ? source + : type.isAssignableFrom(source.getClass()) ? source : conversionService.convert(source, type)); + } + + private String determineDocumentKeyFromId(final String id) { + final String[] split = id.split("/"); + return split[split.length - 1]; + } + + private void addTypeKeyIfNecessary(final TypeInformation definedType, final Object value, final DBEntity sink) { + final Class referenceType = definedType != null ? definedType.getType() : Object.class; + final Class valueType = ClassUtils.getUserClass(value.getClass()); + if (!valueType.equals(referenceType)) { + typeMapper.writeType(valueType, sink); + } + } + + private String convertMapKey(final Object key) { + if (key instanceof String) { + return (String) key; + } + final boolean hasCustomConverter = conversions.hasCustomWriteTarget(key.getClass(), String.class); + return hasCustomConverter ? conversionService.convert(key, String.class) : key.toString(); + } + + private TypeInformation getNonNullComponentType(final TypeInformation type) { + final TypeInformation compType = type.getComponentType(); + return compType != null ? compType : ClassTypeInformation.OBJECT; + } + + private TypeInformation getNonNullMapValueType(final TypeInformation type) { + final TypeInformation valueType = type.getMapValueType(); + return valueType != null ? valueType : ClassTypeInformation.OBJECT; + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/FromResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/FromResolver.java index 4685a4c8b..355cfd1fa 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/FromResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/FromResolver.java @@ -1,67 +1,68 @@ -/* - * 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.core.convert.resolver; - -import java.util.Collection; - -import com.arangodb.model.AqlQueryOptions; -import com.arangodb.springframework.annotation.From; -import com.arangodb.springframework.core.ArangoOperations; -import com.arangodb.util.MapBuilder; - -/** - * @author Mark Vollmary - * - */ -public class FromResolver extends AbstractResolver implements RelationResolver { - - private final ArangoOperations template; - - public FromResolver(final ArangoOperations template) { - super(); - this.template = template; - } - - @Override - public Object resolveOne(final String id, final Class type, final From annotation) { - return annotation.lazy() ? proxy(id, type, annotation, (i, t, a) -> internalResolveOne(i, t)) - : internalResolveOne(id, type); - } - - private Object internalResolveOne(final String id, final Class type) { - return template.find(id, type).get(); - } - - @Override - public Object resolveMultiple(final String id, final Class type, final From annotation) { - return annotation.lazy() - ? proxy(id, Collection.class, annotation, (i, t, a) -> internalResolveMultiple(i, type)) - : internalResolveMultiple(id, type); - } - - private Object internalResolveMultiple(final String id, final Class type) { - return template - .query("FOR e IN @@edge FILTER e._from == @id RETURN e", - new MapBuilder().put("@edge", type).put("id", id).get(), new AqlQueryOptions(), type) - .asListRemaining(); - } - -} +/* + * 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.core.convert.resolver; + +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.annotation.From; +import com.arangodb.springframework.core.ArangoOperations; +import com.arangodb.util.MapBuilder; + +/** + * @author Mark Vollmary + * + */ +public class FromResolver extends AbstractResolver implements RelationResolver { + + private final ArangoOperations template; + + public FromResolver(final ArangoOperations template) { + super(); + this.template = template; + } + + @Override + public Object resolveOne(final String id, final Class type, final From annotation) { + return annotation.lazy() ? proxy(id, type, annotation, (i, t, a) -> internalResolveOne(i, t)) + : internalResolveOne(id, type); + } + + private Object internalResolveOne(final String id, final Class type) { + return template.find(id, type).get(); + } + + @Override + public Object resolveMultiple( + final String id, + final Class type, + final Class containerType, + final From annotation) { + return annotation.lazy() ? proxy(id, containerType, annotation, (i, t, a) -> internalResolveMultiple(i, type)) + : internalResolveMultiple(id, type); + } + + private Object internalResolveMultiple(final String id, final Class type) { + return template + .query("FOR e IN @@edge FILTER e._from == @id RETURN e", + new MapBuilder().put("@edge", type).put("id", id).get(), new AqlQueryOptions(), type) + .asListRemaining(); + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationResolver.java index 0804e8cdd..7a4a90c71 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationResolver.java @@ -1,35 +1,35 @@ -/* - * 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.core.convert.resolver; - -import java.lang.annotation.Annotation; - -/** - * @author Mark Vollmary - * - */ -public interface RelationResolver { - - Object resolveOne(String id, Class type, A annotation); - - Object resolveMultiple(String id, Class type, A annotation); - -} +/* + * 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.core.convert.resolver; + +import java.lang.annotation.Annotation; + +/** + * @author Mark Vollmary + * + */ +public interface RelationResolver { + + Object resolveOne(String id, Class type, A annotation); + + Object resolveMultiple(String id, Class type, Class containerType, A annotation); + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java index 38f7e751f..e4ed10dd7 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java @@ -1,73 +1,74 @@ -/* - * 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.core.convert.resolver; - -import java.util.Arrays; - -import com.arangodb.ArangoCursor; -import com.arangodb.model.AqlQueryOptions; -import com.arangodb.springframework.annotation.Relations; -import com.arangodb.springframework.core.ArangoOperations; -import com.arangodb.util.MapBuilder; - -/** - * @author Mark Vollmary - * @author Christian Lechner - * - */ -public class RelationsResolver extends AbstractResolver - implements RelationResolver, AbstractResolver.ResolverCallback { - - private final ArangoOperations template; - - public RelationsResolver(final ArangoOperations template) { - super(); - this.template = template; - } - - @Override - public Object resolveOne(final String id, final Class type, final Relations annotation) { - throw new UnsupportedOperationException(); - } - - @Override - public Object resolveMultiple(final String id, final Class type, final Relations annotation) { - return annotation.lazy() ? proxy(id, type, annotation, (i, t, a) -> resolve(i, type, a)) - : resolve(id, type, annotation); - } - - @Override - public Object resolve(final String id, final Class type, final Relations annotation) { - final ArangoCursor result = template.query( - "WITH @@vertex FOR v IN " + Math.max(1, annotation.minDepth()) + ".." + Math.max(1, annotation.maxDepth()) - + " " + annotation.direction() - + " @start @@edges OPTIONS {bfs: true, uniqueVertices: \"global\"} RETURN v", - new MapBuilder().put("start", id) - .put("@edges", - Arrays.asList(annotation.edges()).stream().map((e) -> template.collection(e).name()) - .reduce((a, b) -> a + ", " + b).get()) - .put("@vertex", type).get(), - new AqlQueryOptions(), type); - - return result.hasNext() ? result.next() : null; - } - -} +/* + * 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.core.convert.resolver; + +import java.util.Arrays; + +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.annotation.Relations; +import com.arangodb.springframework.core.ArangoOperations; +import com.arangodb.util.MapBuilder; + +/** + * @author Mark Vollmary + * @author Christian Lechner + * + */ +public class RelationsResolver extends AbstractResolver + implements RelationResolver, AbstractResolver.ResolverCallback { + + private final ArangoOperations template; + + public RelationsResolver(final ArangoOperations template) { + super(); + this.template = template; + } + + @Override + public Object resolveOne(final String id, final Class type, final Relations annotation) { + throw new UnsupportedOperationException(); + } + + @Override + public Object resolveMultiple( + final String id, + final Class type, + final Class containerType, + final Relations annotation) { + return annotation.lazy() ? proxy(id, containerType, annotation, (i, t, a) -> resolve(i, type, a)) + : resolve(id, type, annotation); + } + + @Override + public Object resolve(final String id, final Class type, final Relations annotation) { + return template.query( + "WITH @@vertex FOR v IN " + Math.max(1, annotation.minDepth()) + ".." + Math.max(1, annotation.maxDepth()) + + " " + annotation.direction() + + " @start @@edges OPTIONS {bfs: true, uniqueVertices: \"global\"} RETURN v", + new MapBuilder().put("start", id) + .put("@edges", + Arrays.asList(annotation.edges()).stream().map((e) -> template.collection(e).name()) + .reduce((a, b) -> a + ", " + b).get()) + .put("@vertex", type).get(), + new AqlQueryOptions(), type).asListRemaining(); + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/ToResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/ToResolver.java index 59126447e..066f8d190 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/ToResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/ToResolver.java @@ -1,67 +1,68 @@ -/* - * 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.core.convert.resolver; - -import java.util.Collection; - -import com.arangodb.model.AqlQueryOptions; -import com.arangodb.springframework.annotation.To; -import com.arangodb.springframework.core.ArangoOperations; -import com.arangodb.util.MapBuilder; - -/** - * @author Mark Vollmary - * - */ -public class ToResolver extends AbstractResolver implements RelationResolver { - - private final ArangoOperations template; - - public ToResolver(final ArangoOperations template) { - super(); - this.template = template; - } - - @Override - public Object resolveOne(final String id, final Class type, final To annotation) { - return annotation.lazy() ? proxy(id, type, annotation, (i, t, a) -> internalResolveOne(i, t)) - : internalResolveOne(id, type); - } - - private Object internalResolveOne(final String id, final Class type) { - return template.find(id, type).get(); - } - - @Override - public Object resolveMultiple(final String id, final Class type, final To annotation) { - return annotation.lazy() - ? proxy(id, Collection.class, annotation, (i, t, a) -> internalResolveMultiple(i, type)) - : internalResolveMultiple(id, type); - } - - private Object internalResolveMultiple(final String id, final Class type) { - return template - .query("FOR e IN @@edge FILTER e._to == @id RETURN e", - new MapBuilder().put("@edge", type).put("id", id).get(), new AqlQueryOptions(), type) - .asListRemaining(); - } - -} +/* + * 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.core.convert.resolver; + +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.annotation.To; +import com.arangodb.springframework.core.ArangoOperations; +import com.arangodb.util.MapBuilder; + +/** + * @author Mark Vollmary + * + */ +public class ToResolver extends AbstractResolver implements RelationResolver { + + private final ArangoOperations template; + + public ToResolver(final ArangoOperations template) { + super(); + this.template = template; + } + + @Override + public Object resolveOne(final String id, final Class type, final To annotation) { + return annotation.lazy() ? proxy(id, type, annotation, (i, t, a) -> internalResolveOne(i, t)) + : internalResolveOne(id, type); + } + + private Object internalResolveOne(final String id, final Class type) { + return template.find(id, type).get(); + } + + @Override + public Object resolveMultiple( + final String id, + final Class type, + final Class containerType, + final To annotation) { + return annotation.lazy() ? proxy(id, containerType, annotation, (i, t, a) -> internalResolveMultiple(i, type)) + : internalResolveMultiple(id, type); + } + + private Object internalResolveMultiple(final String id, final Class type) { + return template + .query("FOR e IN @@edge FILTER e._to == @id RETURN e", + new MapBuilder().put("@edge", type).put("id", id).get(), new AqlQueryOptions(), type) + .asListRemaining(); + } + +} From ed9abc4f00473129b7af00eede6e7e73f81050b7 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 7 Jun 2018 16:13:01 +0200 Subject: [PATCH 52/94] prepare release 1.1.5 --- ChangeLog | 4 ++++ pom.xml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 7722a2a78..f0e3709a4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +v1.1.5 (2018-06-07) +--------------------------- +* fixed relation cycle (issue #43) + v1.1.4 (2018-06-07) --------------------------- * upgraded arangodb-java-driver to 4.4.1 diff --git a/pom.xml b/pom.xml index 60bef1875..76c9356fa 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.4 + 1.1.5 2017 jar From 4e7c84b54bbd00699005ab49b3b03ad87d0103ea Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 7 Jun 2018 16:35:49 +0200 Subject: [PATCH 53/94] prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 76c9356fa..53e3bd9d4 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.5 + 1.1.6-SNAPSHOT 2017 jar From 966713305246efba5811d6254aaf68e986a15160 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 8 Jun 2018 14:43:38 +0200 Subject: [PATCH 54/94] Fix lazy use of @Relations/@From/@To when using a Set<> (#60) --- ChangeLog | 4 + .../config/AbstractArangoConfiguration.java | 314 +- .../core/convert/DefaultArangoConverter.java | 15 +- .../convert/resolver/AbstractResolver.java | 268 +- .../core/convert/resolver/FromResolver.java | 30 +- .../core/convert/resolver/RefResolver.java | 120 +- .../convert/resolver/ReferenceResolver.java | 74 +- .../convert/resolver/RelationResolver.java | 6 +- .../convert/resolver/RelationsResolver.java | 25 +- .../core/convert/resolver/ToResolver.java | 30 +- .../core/mapping/ArangoMappingTest.java | 2839 ++++++++--------- 11 files changed, 1873 insertions(+), 1852 deletions(-) diff --git a/ChangeLog b/ChangeLog index f0e3709a4..db3476214 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +v1.1.6 (xxxx-xx-xx) +--------------------------- +* fixed lazy use of @Relations/@From/@To when using a Set<> + v1.1.5 (2018-06-07) --------------------------- * fixed relation cycle (issue #43) diff --git a/src/main/java/com/arangodb/springframework/config/AbstractArangoConfiguration.java b/src/main/java/com/arangodb/springframework/config/AbstractArangoConfiguration.java index 4f7fe26d9..d4a717228 100644 --- a/src/main/java/com/arangodb/springframework/config/AbstractArangoConfiguration.java +++ b/src/main/java/com/arangodb/springframework/config/AbstractArangoConfiguration.java @@ -1,155 +1,159 @@ -/* - * 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.config; - -import java.lang.annotation.Annotation; -import java.util.Collections; -import java.util.Optional; -import java.util.Set; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.mapping.model.FieldNamingStrategy; -import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; - -import com.arangodb.ArangoDB; -import com.arangodb.ArangoDBException; -import com.arangodb.springframework.annotation.From; -import com.arangodb.springframework.annotation.Ref; -import com.arangodb.springframework.annotation.Relations; -import com.arangodb.springframework.annotation.To; -import com.arangodb.springframework.core.ArangoOperations; -import com.arangodb.springframework.core.convert.ArangoConverter; -import com.arangodb.springframework.core.convert.ArangoCustomConversions; -import com.arangodb.springframework.core.convert.ArangoTypeMapper; -import com.arangodb.springframework.core.convert.CustomConversions; -import com.arangodb.springframework.core.convert.DBEntityModule; -import com.arangodb.springframework.core.convert.DefaultArangoConverter; -import com.arangodb.springframework.core.convert.DefaultArangoTypeMapper; -import com.arangodb.springframework.core.convert.resolver.FromResolver; -import com.arangodb.springframework.core.convert.resolver.RefResolver; -import com.arangodb.springframework.core.convert.resolver.ReferenceResolver; -import com.arangodb.springframework.core.convert.resolver.RelationResolver; -import com.arangodb.springframework.core.convert.resolver.RelationsResolver; -import com.arangodb.springframework.core.convert.resolver.ResolverFactory; -import com.arangodb.springframework.core.convert.resolver.ToResolver; -import com.arangodb.springframework.core.mapping.ArangoMappingContext; -import com.arangodb.springframework.core.template.ArangoTemplate; -import com.arangodb.velocypack.module.jdk8.VPackJdk8Module; -import com.arangodb.velocypack.module.joda.VPackJodaModule; - -/** - * @author Mark Vollmary - * @author Christian Lechner - * - */ -@Configuration -public abstract class AbstractArangoConfiguration { - - protected abstract ArangoDB.Builder arango(); - - protected abstract String database(); - - private ArangoDB.Builder configure(final ArangoDB.Builder arango) { - return arango.registerModules(new VPackJdk8Module(), new VPackJodaModule(), new DBEntityModule()); - } - - @Bean - public ArangoOperations arangoTemplate() throws Exception { - return new ArangoTemplate(configure(arango()), database(), arangoConverter()); - } - - @Bean - public ArangoMappingContext arangoMappingContext() throws Exception { - final ArangoMappingContext context = new ArangoMappingContext(); - context.setInitialEntitySet(getInitialEntitySet()); - context.setFieldNamingStrategy(fieldNamingStrategy()); - context.setSimpleTypeHolder(customConversions().getSimpleTypeHolder()); - return context; - } - - @Bean - public ArangoConverter arangoConverter() throws Exception { - return new DefaultArangoConverter(arangoMappingContext(), customConversions(), resolverFactory(), - arangoTypeMapper()); - } - - protected CustomConversions customConversions() { - return new ArangoCustomConversions(Collections.emptyList()); - } - - private Set> getInitialEntitySet() throws ClassNotFoundException { - return ArangoEntityClassScanner.scanForEntities(getEntityBasePackages()); - } - - protected String[] getEntityBasePackages() { - return new String[] { getClass().getPackage().getName() }; - } - - protected FieldNamingStrategy fieldNamingStrategy() { - return PropertyNameFieldNamingStrategy.INSTANCE; - } - - protected String typeKey() { - return DefaultArangoTypeMapper.DEFAULT_TYPE_KEY; - } - - protected ArangoTypeMapper arangoTypeMapper() throws Exception { - return new DefaultArangoTypeMapper(typeKey(), arangoMappingContext()); - } - - protected ResolverFactory resolverFactory() { - return new ResolverFactory() { - @SuppressWarnings("unchecked") - @Override - public Optional> getReferenceResolver(final A annotation) { - ReferenceResolver resolver = null; - try { - if (annotation instanceof Ref) { - resolver = (ReferenceResolver) new RefResolver(arangoTemplate()); - } - } catch (final Exception e) { - throw new ArangoDBException(e); - } - return Optional.ofNullable(resolver); - } - - @SuppressWarnings("unchecked") - @Override - public Optional> getRelationResolver(final A annotation) { - RelationResolver resolver = null; - try { - if (annotation instanceof From) { - resolver = (RelationResolver) new FromResolver(arangoTemplate()); - } else if (annotation instanceof To) { - resolver = (RelationResolver) new ToResolver(arangoTemplate()); - } else if (annotation instanceof Relations) { - resolver = (RelationResolver) new RelationsResolver(arangoTemplate()); - } - } catch (final Exception e) { - throw new ArangoDBException(e); - } - return Optional.ofNullable(resolver); - } - }; - } - -} +/* + * 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.config; + +import java.lang.annotation.Annotation; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mapping.model.FieldNamingStrategy; +import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; + +import com.arangodb.ArangoDB; +import com.arangodb.ArangoDBException; +import com.arangodb.springframework.annotation.From; +import com.arangodb.springframework.annotation.Ref; +import com.arangodb.springframework.annotation.Relations; +import com.arangodb.springframework.annotation.To; +import com.arangodb.springframework.core.ArangoOperations; +import com.arangodb.springframework.core.convert.ArangoConverter; +import com.arangodb.springframework.core.convert.ArangoCustomConversions; +import com.arangodb.springframework.core.convert.ArangoTypeMapper; +import com.arangodb.springframework.core.convert.CustomConversions; +import com.arangodb.springframework.core.convert.DBEntityModule; +import com.arangodb.springframework.core.convert.DefaultArangoConverter; +import com.arangodb.springframework.core.convert.DefaultArangoTypeMapper; +import com.arangodb.springframework.core.convert.resolver.FromResolver; +import com.arangodb.springframework.core.convert.resolver.RefResolver; +import com.arangodb.springframework.core.convert.resolver.ReferenceResolver; +import com.arangodb.springframework.core.convert.resolver.RelationResolver; +import com.arangodb.springframework.core.convert.resolver.RelationsResolver; +import com.arangodb.springframework.core.convert.resolver.ResolverFactory; +import com.arangodb.springframework.core.convert.resolver.ToResolver; +import com.arangodb.springframework.core.mapping.ArangoMappingContext; +import com.arangodb.springframework.core.template.ArangoTemplate; +import com.arangodb.velocypack.module.jdk8.VPackJdk8Module; +import com.arangodb.velocypack.module.joda.VPackJodaModule; + +/** + * @author Mark Vollmary + * @author Christian Lechner + * + */ +@Configuration +public abstract class AbstractArangoConfiguration { + + protected abstract ArangoDB.Builder arango(); + + protected abstract String database(); + + private ArangoDB.Builder configure(final ArangoDB.Builder arango) { + return arango.registerModules(new VPackJdk8Module(), new VPackJodaModule(), new DBEntityModule()); + } + + @Bean + public ArangoOperations arangoTemplate() throws Exception { + return new ArangoTemplate(configure(arango()), database(), arangoConverter()); + } + + @Bean + public ArangoMappingContext arangoMappingContext() throws Exception { + final ArangoMappingContext context = new ArangoMappingContext(); + context.setInitialEntitySet(getInitialEntitySet()); + context.setFieldNamingStrategy(fieldNamingStrategy()); + context.setSimpleTypeHolder(customConversions().getSimpleTypeHolder()); + return context; + } + + @Bean + public ArangoConverter arangoConverter() throws Exception { + return new DefaultArangoConverter(arangoMappingContext(), customConversions(), resolverFactory(), + arangoTypeMapper()); + } + + protected CustomConversions customConversions() { + return new ArangoCustomConversions(Collections.emptyList()); + } + + private Set> getInitialEntitySet() throws ClassNotFoundException { + return ArangoEntityClassScanner.scanForEntities(getEntityBasePackages()); + } + + protected String[] getEntityBasePackages() { + return new String[] { getClass().getPackage().getName() }; + } + + protected FieldNamingStrategy fieldNamingStrategy() { + return PropertyNameFieldNamingStrategy.INSTANCE; + } + + protected String typeKey() { + return DefaultArangoTypeMapper.DEFAULT_TYPE_KEY; + } + + protected ArangoTypeMapper arangoTypeMapper() throws Exception { + return new DefaultArangoTypeMapper(typeKey(), arangoMappingContext()); + } + + protected ResolverFactory resolverFactory() { + return new ResolverFactory() { + @SuppressWarnings("unchecked") + @Override + public Optional> getReferenceResolver(final A annotation) { + ReferenceResolver resolver = null; + try { + if (annotation instanceof Ref) { + resolver = (ReferenceResolver) new RefResolver(arangoTemplate(), + arangoConverter().getConversionService()); + } + } catch (final Exception e) { + throw new ArangoDBException(e); + } + return Optional.ofNullable(resolver); + } + + @SuppressWarnings("unchecked") + @Override + public Optional> getRelationResolver(final A annotation) { + RelationResolver resolver = null; + try { + if (annotation instanceof From) { + resolver = (RelationResolver) new FromResolver(arangoTemplate(), + arangoConverter().getConversionService()); + } else if (annotation instanceof To) { + resolver = (RelationResolver) new ToResolver(arangoTemplate(), + arangoConverter().getConversionService()); + } else if (annotation instanceof Relations) { + resolver = (RelationResolver) new RelationsResolver(arangoTemplate(), + arangoConverter().getConversionService()); + } + } catch (final Exception e) { + throw new ArangoDBException(e); + } + return Optional.ofNullable(resolver); + } + }; + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java index ab0c88e94..355095e21 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java @@ -265,15 +265,14 @@ private Optional readReference( throw new MappingException( "Collection of Type String expected for references but found type " + source.getClass()); } - return Optional.ofNullable(resolver.resolveMultiple(ids, - getNonNullComponentType(property.getTypeInformation()).getType(), annotation)); + return Optional.ofNullable(resolver.resolveMultiple(ids, property.getTypeInformation(), annotation)); } else { if (!(source instanceof String)) { throw new MappingException( "Type String expected for reference but found type " + source.getClass()); } - return Optional.ofNullable( - resolver.resolveOne(source.toString(), property.getTypeInformation().getType(), annotation)); + return Optional + .ofNullable(resolver.resolveOne(source.toString(), property.getTypeInformation(), annotation)); } }); } @@ -285,12 +284,10 @@ private Optional readRelation( final A annotation) { return resolverFactory.getRelationResolver(annotation).flatMap(resolver -> { if (property.isCollectionLike() && parentId != null) { - return Optional.of(resolver.resolveMultiple(parentId.toString(), - getNonNullComponentType(property.getTypeInformation()).getType(), - property.getTypeInformation().getType(), annotation)); + return Optional + .of(resolver.resolveMultiple(parentId.toString(), property.getTypeInformation(), annotation)); } else if (source != null) { - return Optional.of( - resolver.resolveOne(source.toString(), property.getTypeInformation().getType(), annotation)); + return Optional.of(resolver.resolveOne(source.toString(), property.getTypeInformation(), annotation)); } return Optional.empty(); }); diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/AbstractResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/AbstractResolver.java index d3d08a6ff..f5443fb4f 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/AbstractResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/AbstractResolver.java @@ -1,125 +1,143 @@ -/* - * 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.core.convert.resolver; - -import java.io.Serializable; -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; - -import org.aopalliance.intercept.MethodInvocation; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.cglib.proxy.Callback; -import org.springframework.cglib.proxy.Enhancer; -import org.springframework.cglib.proxy.Factory; -import org.springframework.cglib.proxy.MethodProxy; -import org.springframework.objenesis.ObjenesisStd; - -/** - * @author Mark Vollmary - * - */ -public abstract class AbstractResolver { - - private final ObjenesisStd objenesis; - - protected AbstractResolver() { - super(); - this.objenesis = new ObjenesisStd(true); - } - - static interface ResolverCallback { - - Object resolve(String id, Class type, A annotation); - - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - protected Object proxy( - final String id, - final Class type, - final A annotation, - final ResolverCallback callback) { - final ProxyInterceptor interceptor = new ProxyInterceptor(id, type, annotation, callback); - if (type.isInterface()) { - final ProxyFactory proxyFactory = new ProxyFactory(new Class[] { type }); - for (final Class interf : type.getInterfaces()) { - proxyFactory.addInterface(interf); - } - proxyFactory.addAdvice(interceptor); - return proxyFactory.getProxy(); - } else { - final Factory factory = (Factory) objenesis.newInstance(enhancedTypeFor(type)); - factory.setCallbacks(new Callback[] { interceptor }); - return factory; - } - } - - private Class enhancedTypeFor(final Class type) { - final Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(type); - enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); - return enhancer.createClass(); - } - - static class ProxyInterceptor implements Serializable, - org.springframework.cglib.proxy.MethodInterceptor, org.aopalliance.intercept.MethodInterceptor { - - private static final long serialVersionUID = -6722757823918987065L; - private final String id; - private final Class type; - private final A annotation; - private final ResolverCallback callback; - private volatile boolean resolved; - private Object result; - - public ProxyInterceptor(final String id, final Class type, final A annotation, - final ResolverCallback callback) { - super(); - this.id = id; - this.type = type; - this.annotation = annotation; - this.callback = callback; - result = null; - resolved = false; - } - - @Override - public Object invoke(final MethodInvocation invocation) throws Throwable { - return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null); - } - - @Override - public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy proxy) - throws Throwable { - final Object result = resolve(); - return result == null ? null : method.invoke(result, args); - } - - private synchronized Object resolve() { - if (!resolved) { - result = callback.resolve(id, type, annotation); - resolved = true; - } - return result; - } - - } -} +/* + * 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.core.convert.resolver; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; + +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.cglib.proxy.Callback; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.Factory; +import org.springframework.cglib.proxy.MethodProxy; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; +import org.springframework.objenesis.ObjenesisStd; + +/** + * @author Mark Vollmary + * + */ +public abstract class AbstractResolver { + + private final ObjenesisStd objenesis; + private final ConversionService conversionService; + + protected AbstractResolver(final ConversionService conversionService) { + super(); + this.conversionService = conversionService; + this.objenesis = new ObjenesisStd(true); + } + + static interface ResolverCallback { + + Object resolve(String id, TypeInformation type, A annotation); + + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected Object proxy( + final String id, + final TypeInformation type, + final A annotation, + final ResolverCallback callback) { + final ProxyInterceptor interceptor = new ProxyInterceptor(id, type, annotation, callback, conversionService); + if (type.getType().isInterface()) { + final ProxyFactory proxyFactory = new ProxyFactory(new Class[] { type.getType() }); + for (final Class interf : type.getType().getInterfaces()) { + proxyFactory.addInterface(interf); + } + proxyFactory.addAdvice(interceptor); + return proxyFactory.getProxy(); + } else { + final Factory factory = (Factory) objenesis.newInstance(enhancedTypeFor(type.getType())); + factory.setCallbacks(new Callback[] { interceptor }); + return factory; + } + } + + private Class enhancedTypeFor(final Class type) { + final Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(type); + enhancer.setCallbackType(org.springframework.cglib.proxy.MethodInterceptor.class); + return enhancer.createClass(); + } + + static class ProxyInterceptor implements Serializable, + org.springframework.cglib.proxy.MethodInterceptor, org.aopalliance.intercept.MethodInterceptor { + + private static final long serialVersionUID = -6722757823918987065L; + private final String id; + final TypeInformation type; + private final A annotation; + private final ResolverCallback callback; + private volatile boolean resolved; + private Object result; + private final ConversionService conversionService; + + public ProxyInterceptor(final String id, final TypeInformation type, final A annotation, + final ResolverCallback callback, final ConversionService conversionService) { + super(); + this.id = id; + this.type = type; + this.annotation = annotation; + this.callback = callback; + this.conversionService = conversionService; + result = null; + resolved = false; + } + + @Override + public Object invoke(final MethodInvocation invocation) throws Throwable { + return intercept(invocation.getThis(), invocation.getMethod(), invocation.getArguments(), null); + } + + @Override + public Object intercept(final Object obj, final Method method, final Object[] args, final MethodProxy proxy) + throws Throwable { + final Object result = resolve(); + return result == null ? null : method.invoke(result, args); + } + + private synchronized Object resolve() { + if (!resolved) { + result = convertIfNecessary(callback.resolve(id, type, annotation), type.getType()); + resolved = true; + } + return result; + } + + @SuppressWarnings("unchecked") + private T convertIfNecessary(final Object source, final Class type) { + return (T) (source == null ? null + : type.isAssignableFrom(source.getClass()) ? source : conversionService.convert(source, type)); + } + } + + protected static TypeInformation getNonNullComponentType(final TypeInformation type) { + final TypeInformation compType = type.getComponentType(); + return compType != null ? compType : ClassTypeInformation.OBJECT; + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/FromResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/FromResolver.java index 355cfd1fa..12aa2d67a 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/FromResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/FromResolver.java @@ -20,6 +20,9 @@ package com.arangodb.springframework.core.convert.resolver; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.util.TypeInformation; + import com.arangodb.model.AqlQueryOptions; import com.arangodb.springframework.annotation.From; import com.arangodb.springframework.core.ArangoOperations; @@ -33,36 +36,31 @@ public class FromResolver extends AbstractResolver implements RelationReso private final ArangoOperations template; - public FromResolver(final ArangoOperations template) { - super(); + public FromResolver(final ArangoOperations template, final ConversionService conversionService) { + super(conversionService); this.template = template; } @Override - public Object resolveOne(final String id, final Class type, final From annotation) { + public Object resolveOne(final String id, final TypeInformation type, final From annotation) { return annotation.lazy() ? proxy(id, type, annotation, (i, t, a) -> internalResolveOne(i, t)) : internalResolveOne(id, type); } - private Object internalResolveOne(final String id, final Class type) { - return template.find(id, type).get(); + private Object internalResolveOne(final String id, final TypeInformation type) { + return template.find(id, type.getType()).get(); } @Override - public Object resolveMultiple( - final String id, - final Class type, - final Class containerType, - final From annotation) { - return annotation.lazy() ? proxy(id, containerType, annotation, (i, t, a) -> internalResolveMultiple(i, type)) + public Object resolveMultiple(final String id, final TypeInformation type, final From annotation) { + return annotation.lazy() ? proxy(id, type, annotation, (i, t, a) -> internalResolveMultiple(i, t)) : internalResolveMultiple(id, type); } - private Object internalResolveMultiple(final String id, final Class type) { - return template - .query("FOR e IN @@edge FILTER e._from == @id RETURN e", - new MapBuilder().put("@edge", type).put("id", id).get(), new AqlQueryOptions(), type) - .asListRemaining(); + private Object internalResolveMultiple(final String id, final TypeInformation type) { + final Class t = getNonNullComponentType(type).getType(); + return template.query("FOR e IN @@edge FILTER e._from == @id RETURN e", + new MapBuilder().put("@edge", t).put("id", id).get(), new AqlQueryOptions(), t).asListRemaining(); } } diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/RefResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/RefResolver.java index 0863e607c..4ab5f2eaa 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/RefResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/RefResolver.java @@ -1,58 +1,62 @@ -/* - * 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.core.convert.resolver; - -import java.util.Collection; -import java.util.stream.Collectors; - -import com.arangodb.springframework.annotation.Ref; -import com.arangodb.springframework.core.ArangoOperations; - -/** - * @author Mark Vollmary - * - */ -public class RefResolver extends AbstractResolver - implements ReferenceResolver, AbstractResolver.ResolverCallback { - - private final ArangoOperations template; - - public RefResolver(final ArangoOperations template) { - super(); - this.template = template; - } - - @Override - public Object resolveOne(final String id, final Class type, final Ref annotation) { - return annotation.lazy() ? proxy(id, type, annotation, this) : resolve(id, type, annotation); - } - - @Override - public Object resolveMultiple(final Collection ids, final Class type, final Ref annotation) { - return ids.stream().map(id -> resolveOne(id, type, annotation)).collect(Collectors.toList()); - } - - @Override - public Object resolve(final String id, final Class type, final Ref annotation) { - return template.find(id, type).get(); - } - -} +/* + * 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.core.convert.resolver; + +import java.util.Collection; +import java.util.stream.Collectors; + +import org.springframework.core.convert.ConversionService; +import org.springframework.data.util.TypeInformation; + +import com.arangodb.springframework.annotation.Ref; +import com.arangodb.springframework.core.ArangoOperations; + +/** + * @author Mark Vollmary + * + */ +public class RefResolver extends AbstractResolver + implements ReferenceResolver, AbstractResolver.ResolverCallback { + + private final ArangoOperations template; + + public RefResolver(final ArangoOperations template, final ConversionService conversionService) { + super(conversionService); + this.template = template; + } + + @Override + public Object resolveOne(final String id, final TypeInformation type, final Ref annotation) { + return annotation.lazy() ? proxy(id, type, annotation, this) : resolve(id, type, annotation); + } + + @Override + public Object resolveMultiple(final Collection ids, final TypeInformation type, final Ref annotation) { + return ids.stream().map(id -> resolveOne(id, getNonNullComponentType(type), annotation)) + .collect(Collectors.toList()); + } + + @Override + public Object resolve(final String id, final TypeInformation type, final Ref annotation) { + return template.find(id, type.getType()).get(); + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/ReferenceResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/ReferenceResolver.java index 6a066fede..be370a66e 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/ReferenceResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/ReferenceResolver.java @@ -1,36 +1,38 @@ -/* - * 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.core.convert.resolver; - -import java.lang.annotation.Annotation; -import java.util.Collection; - -/** - * @author Mark Vollmary - * - */ -public interface ReferenceResolver { - - Object resolveOne(String id, Class type, A annotation); - - Object resolveMultiple(Collection ids, Class type, A annotation); - -} +/* + * 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.core.convert.resolver; + +import java.lang.annotation.Annotation; +import java.util.Collection; + +import org.springframework.data.util.TypeInformation; + +/** + * @author Mark Vollmary + * + */ +public interface ReferenceResolver { + + Object resolveOne(String id, TypeInformation type, A annotation); + + Object resolveMultiple(Collection ids, TypeInformation type, A annotation); + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationResolver.java index 7a4a90c71..363fd3b59 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationResolver.java @@ -22,14 +22,16 @@ import java.lang.annotation.Annotation; +import org.springframework.data.util.TypeInformation; + /** * @author Mark Vollmary * */ public interface RelationResolver { - Object resolveOne(String id, Class type, A annotation); + Object resolveOne(String id, TypeInformation type, A annotation); - Object resolveMultiple(String id, Class type, Class containerType, A annotation); + Object resolveMultiple(String id, TypeInformation type, A annotation); } diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java index e4ed10dd7..21b33d121 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/RelationsResolver.java @@ -22,6 +22,9 @@ import java.util.Arrays; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.util.TypeInformation; + import com.arangodb.model.AqlQueryOptions; import com.arangodb.springframework.annotation.Relations; import com.arangodb.springframework.core.ArangoOperations; @@ -37,28 +40,24 @@ public class RelationsResolver extends AbstractResolver private final ArangoOperations template; - public RelationsResolver(final ArangoOperations template) { - super(); + public RelationsResolver(final ArangoOperations template, final ConversionService conversionService) { + super(conversionService); this.template = template; } @Override - public Object resolveOne(final String id, final Class type, final Relations annotation) { + public Object resolveOne(final String id, final TypeInformation type, final Relations annotation) { throw new UnsupportedOperationException(); } @Override - public Object resolveMultiple( - final String id, - final Class type, - final Class containerType, - final Relations annotation) { - return annotation.lazy() ? proxy(id, containerType, annotation, (i, t, a) -> resolve(i, type, a)) - : resolve(id, type, annotation); + public Object resolveMultiple(final String id, final TypeInformation type, final Relations annotation) { + return annotation.lazy() ? proxy(id, type, annotation, this) : resolve(id, type, annotation); } @Override - public Object resolve(final String id, final Class type, final Relations annotation) { + public Object resolve(final String id, final TypeInformation type, final Relations annotation) { + final Class t = getNonNullComponentType(type).getType(); return template.query( "WITH @@vertex FOR v IN " + Math.max(1, annotation.minDepth()) + ".." + Math.max(1, annotation.maxDepth()) + " " + annotation.direction() @@ -67,8 +66,8 @@ public Object resolve(final String id, final Class type, final Relations anno .put("@edges", Arrays.asList(annotation.edges()).stream().map((e) -> template.collection(e).name()) .reduce((a, b) -> a + ", " + b).get()) - .put("@vertex", type).get(), - new AqlQueryOptions(), type).asListRemaining(); + .put("@vertex", t).get(), + new AqlQueryOptions(), t).asListRemaining(); } } diff --git a/src/main/java/com/arangodb/springframework/core/convert/resolver/ToResolver.java b/src/main/java/com/arangodb/springframework/core/convert/resolver/ToResolver.java index 066f8d190..280ae5935 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/resolver/ToResolver.java +++ b/src/main/java/com/arangodb/springframework/core/convert/resolver/ToResolver.java @@ -20,6 +20,9 @@ package com.arangodb.springframework.core.convert.resolver; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.util.TypeInformation; + import com.arangodb.model.AqlQueryOptions; import com.arangodb.springframework.annotation.To; import com.arangodb.springframework.core.ArangoOperations; @@ -33,36 +36,31 @@ public class ToResolver extends AbstractResolver implements RelationResolver private final ArangoOperations template; - public ToResolver(final ArangoOperations template) { - super(); + public ToResolver(final ArangoOperations template, final ConversionService conversionService) { + super(conversionService); this.template = template; } @Override - public Object resolveOne(final String id, final Class type, final To annotation) { + public Object resolveOne(final String id, final TypeInformation type, final To annotation) { return annotation.lazy() ? proxy(id, type, annotation, (i, t, a) -> internalResolveOne(i, t)) : internalResolveOne(id, type); } - private Object internalResolveOne(final String id, final Class type) { - return template.find(id, type).get(); + private Object internalResolveOne(final String id, final TypeInformation type) { + return template.find(id, type.getType()).get(); } @Override - public Object resolveMultiple( - final String id, - final Class type, - final Class containerType, - final To annotation) { - return annotation.lazy() ? proxy(id, containerType, annotation, (i, t, a) -> internalResolveMultiple(i, type)) + public Object resolveMultiple(final String id, final TypeInformation type, final To annotation) { + return annotation.lazy() ? proxy(id, type, annotation, (i, t, a) -> internalResolveMultiple(i, t)) : internalResolveMultiple(id, type); } - private Object internalResolveMultiple(final String id, final Class type) { - return template - .query("FOR e IN @@edge FILTER e._to == @id RETURN e", - new MapBuilder().put("@edge", type).put("id", id).get(), new AqlQueryOptions(), type) - .asListRemaining(); + private Object internalResolveMultiple(final String id, final TypeInformation type) { + final Class t = getNonNullComponentType(type).getType(); + return template.query("FOR e IN @@edge FILTER e._to == @id RETURN e", + new MapBuilder().put("@edge", t).put("id", id).get(), new AqlQueryOptions(), t).asListRemaining(); } } diff --git a/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java b/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java index ce06cab61..92afcf062 100644 --- a/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java +++ b/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java @@ -1,1422 +1,1417 @@ -/* - * 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.core.mapping; - -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.isOneOf; -import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; - -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.joda.time.DateTimeZone; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.data.annotation.Id; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import com.arangodb.entity.DocumentEntity; -import com.arangodb.model.AqlQueryOptions; -import com.arangodb.springframework.AbstractArangoTest; -import com.arangodb.springframework.ArangoTestConfiguration; -import com.arangodb.springframework.annotation.Document; -import com.arangodb.springframework.annotation.Edge; -import com.arangodb.springframework.annotation.Field; -import com.arangodb.springframework.annotation.From; -import com.arangodb.springframework.annotation.Key; -import com.arangodb.springframework.annotation.Ref; -import com.arangodb.springframework.annotation.Relations; -import com.arangodb.springframework.annotation.Rev; -import com.arangodb.springframework.annotation.To; -import com.arangodb.util.MapBuilder; -import com.arangodb.velocypack.VPackSlice; - -/** - * @author Mark Vollmary - * @author Christian Lechner - * - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = { ArangoTestConfiguration.class }) -public class ArangoMappingTest extends AbstractArangoTest { - - @Document - public static class BasicTestEntity { - - @Id - String id; - @Key - String key; - @Rev - String rev; - - public BasicTestEntity() { - super(); - } - - public String getId() { - return id; - } - - public void setId(final String id) { - this.id = id; - } - - public String getKey() { - return key; - } - - public void setKey(final String key) { - this.key = key; - } - - public String getRev() { - return rev; - } - - public void setRev(final String rev) { - this.rev = rev; - } - - } - - @Test - public void idKeyRev() { - final DocumentEntity ref = template.insert(new BasicTestEntity()); - final BasicTestEntity entity = template.find(ref.getId(), BasicTestEntity.class).get(); - assertThat(entity, is(notNullValue())); - assertThat(entity.getId(), is(ref.getId())); - assertThat(entity.getKey(), is(ref.getKey())); - assertThat(entity.getRev(), is(ref.getRev())); - } - - public static class OnlyIdTestEntity { - @Id - private String id; - } - - @Test - public void supplementKey() { - final OnlyIdTestEntity value = new OnlyIdTestEntity(); - template.insert(value); - final List result = template.query("RETURN @doc", new MapBuilder().put("doc", value).get(), - new AqlQueryOptions(), BasicTestEntity.class).asListRemaining(); - assertThat(result.size(), is(1)); - assertThat(result.get(0).getId(), is(value.id)); - assertThat(result.get(0).getKey(), is(value.id.split("/")[1])); - assertThat(result.get(0).getRev(), is(nullValue())); - } - - public static class FieldNameTestEntity extends BasicTestEntity { - @Field("alt-test") - private String test; - } - - @Test - public void fieldNameAnnotation() { - final FieldNameTestEntity entity = new FieldNameTestEntity(); - entity.test = "1234"; - final DocumentEntity res = template.insert(entity); - final VPackSlice slice = template.driver().db(ArangoTestConfiguration.DB).getDocument(res.getId(), - VPackSlice.class); - assertThat(slice, is(notNullValue())); - assertThat(slice.get("alt-test").isString(), is(true)); - assertThat(slice.get("alt-test").getAsString(), is(entity.test)); - } - - public static class SingleNestedDocumentTestEntity extends BasicTestEntity { - private NestedDocumentTestEntity entity; - } - - public static class NestedDocumentTestEntity { - private String test; - - public NestedDocumentTestEntity() { - super(); - } - - public NestedDocumentTestEntity(final String test) { - super(); - this.test = test; - } - } - - @Test - public void singleNestedDocument() { - final SingleNestedDocumentTestEntity entity = new SingleNestedDocumentTestEntity(); - entity.entity = new NestedDocumentTestEntity("test"); - template.insert(entity); - final SingleNestedDocumentTestEntity document = template.find(entity.id, SingleNestedDocumentTestEntity.class) - .get(); - assertThat(document, is(notNullValue())); - assertThat(document.entity, is(notNullValue())); - assertThat(document.entity.test, is("test")); - } - - public static class MultipleNestedDocumentTestEntity extends BasicTestEntity { - private Collection entities; - } - - @Test - public void multipleNestedDocuments() { - final MultipleNestedDocumentTestEntity entity = new MultipleNestedDocumentTestEntity(); - entity.entities = new ArrayList<>(Arrays.asList(new NestedDocumentTestEntity("0"), - new NestedDocumentTestEntity("1"), new NestedDocumentTestEntity("2"))); - template.insert(entity); - final MultipleNestedDocumentTestEntity document = template - .find(entity.id, MultipleNestedDocumentTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - assertThat(document.entities.size(), is(3)); - assertThat(document.entities.stream().map(e -> e.test).collect(Collectors.toList()), hasItems("0", "1", "2")); - } - - public static class MultipleNestedCollectionsTestEntity extends BasicTestEntity { - private Collection> entities; - } - - @Test - public void multipleNestedCollections() { - final MultipleNestedCollectionsTestEntity entity = new MultipleNestedCollectionsTestEntity(); - entity.entities = new ArrayList<>(Arrays.asList( - new ArrayList<>(Arrays.asList(new NestedDocumentTestEntity("00"), new NestedDocumentTestEntity("01"), - new NestedDocumentTestEntity("02"))), - new ArrayList<>(Arrays.asList(new NestedDocumentTestEntity("10"), new NestedDocumentTestEntity("11"), - new NestedDocumentTestEntity("12"))))); - template.insert(entity); - final MultipleNestedCollectionsTestEntity document = template - .find(entity.id, MultipleNestedCollectionsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - assertThat(document.entities.size(), is(2)); - final List> collect = document.entities.stream() - .map(c -> c.stream().map(cc -> cc.test).collect(Collectors.toList())).collect(Collectors.toList()); - for (int i = 0; i < collect.size(); i++) { - for (int j = 0; j < collect.get(i).size(); j++) { - assertThat(collect.get(i).get(j), is(i + "" + j)); - } - } - } - - public static class SingleNestedMapTestEntity extends BasicTestEntity { - private Map entities; - } - - @Test - public void singleNestedMap() { - final SingleNestedMapTestEntity entity = new SingleNestedMapTestEntity(); - entity.entities = new HashMap<>(); - entity.entities.put("0", new NestedDocumentTestEntity("0")); - entity.entities.put("1", new NestedDocumentTestEntity("1")); - entity.entities.put("2", new NestedDocumentTestEntity("2")); - template.insert(entity); - final SingleNestedMapTestEntity document = template.find(entity.id, SingleNestedMapTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - final Map collect = document.entities.entrySet().stream() - .map(e -> new String[] { e.getKey(), e.getValue().test }) - .collect(Collectors.toMap(k -> k[0], v -> v[1])); - for (int i = 0; i <= 2; i++) { - assertThat(collect.get(String.valueOf(i)), is(String.valueOf(i))); - } - } - - public static class MultipleNestedMapTestEntity extends BasicTestEntity { - private Map> entities; - } - - @Test - public void multipleNestedMaps() { - final MultipleNestedMapTestEntity entity = new MultipleNestedMapTestEntity(); - entity.entities = new HashMap<>(); - final Map m0 = new HashMap<>(); - m0.put("0", new NestedDocumentTestEntity("00")); - m0.put("1", new NestedDocumentTestEntity("01")); - m0.put("2", new NestedDocumentTestEntity("02")); - entity.entities.put("0", m0); - final Map m1 = new HashMap<>(); - m1.put("0", new NestedDocumentTestEntity("10")); - m1.put("1", new NestedDocumentTestEntity("11")); - m1.put("2", new NestedDocumentTestEntity("12")); - entity.entities.put("1", m1); - template.insert(entity); - final MultipleNestedMapTestEntity document = template.find(entity.id, MultipleNestedMapTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - for (int i = 0; i <= 1; i++) { - for (int j = 0; j <= 2; j++) { - assertThat(document.entities.get(String.valueOf(i)).get(String.valueOf(j)).test, is(i + "" + j)); - } - } - } - - public static class SingleReferenceTestEntity extends BasicTestEntity { - @Ref - private BasicTestEntity entity; - } - - @Test - public void singleRef() { - final BasicTestEntity e1 = new BasicTestEntity(); - template.insert(e1); - final SingleReferenceTestEntity e0 = new SingleReferenceTestEntity(); - e0.entity = e1; - template.insert(e0); - final SingleReferenceTestEntity document = template.find(e0.id, SingleReferenceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entity, is(notNullValue())); - assertThat(document.entity.id, is(e1.id)); - } - - public static class SingleReferenceLazyTestEntity extends BasicTestEntity { - @Ref(lazy = true) - private BasicTestEntity entity; - } - - @Test - public void singleRefLazy() { - final BasicTestEntity e1 = new BasicTestEntity(); - template.insert(e1); - final SingleReferenceLazyTestEntity e0 = new SingleReferenceLazyTestEntity(); - e0.entity = e1; - template.insert(e0); - final SingleReferenceLazyTestEntity document = template.find(e0.id, SingleReferenceLazyTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entity, is(notNullValue())); - assertThat(document.entity, instanceOf(BasicTestEntity.class)); - assertThat(document.entity.getId(), is(e1.getId())); - } - - public static class MultiReferenceTestEntity extends BasicTestEntity { - @Ref - private Collection entities; - } - - @Test - public void multiRef() { - final BasicTestEntity e1 = new BasicTestEntity(); - template.insert(e1); - final BasicTestEntity e2 = new BasicTestEntity(); - template.insert(e2); - final MultiReferenceTestEntity e0 = new MultiReferenceTestEntity(); - e0.entities = Arrays.asList(e1, e2); - template.insert(e0); - final MultiReferenceTestEntity document = template.find(e0.id, MultiReferenceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - assertThat(document.entities.size(), is(2)); - for (final BasicTestEntity e : document.entities) { - assertThat(e, instanceOf(BasicTestEntity.class)); - assertThat(e.getId(), is(notNullValue())); - assertThat(e.getId(), is(isOneOf(e1.getId(), e2.getId()))); - } - } - - public static class MultiReferenceLazyTestEntity extends BasicTestEntity { - @Ref(lazy = true) - private Collection entities; - } - - @Test - public void multiRefLazy() { - final BasicTestEntity e1 = new BasicTestEntity(); - template.insert(e1); - final BasicTestEntity e2 = new BasicTestEntity(); - template.insert(e2); - final MultiReferenceLazyTestEntity e0 = new MultiReferenceLazyTestEntity(); - e0.entities = Arrays.asList(e1, e2); - template.insert(e0); - final MultiReferenceLazyTestEntity document = template.find(e0.id, MultiReferenceLazyTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - assertThat(document.entities.size(), is(2)); - for (final BasicTestEntity e : document.entities) { - assertThat(e, instanceOf(BasicTestEntity.class)); - assertThat(e.getId(), is(notNullValue())); - assertThat(e.getId(), is(isOneOf(e1.getId(), e2.getId()))); - } - } - - public static class NestedReferenceTestEntity extends BasicTestEntity { - private NestedReferenceSubTestEntity sub; - } - - public static class NestedReferenceSubTestEntity { - @Ref - private Collection entities; - } - - @Test - public void testNestedRef() { - final NestedReferenceTestEntity o = new NestedReferenceTestEntity(); - o.sub = new NestedReferenceSubTestEntity(); - o.sub.entities = new ArrayList<>(); - final BasicTestEntity e = new BasicTestEntity(); - o.sub.entities.add(e); - template.insert(e); - template.insert(o); - final NestedReferenceTestEntity document = template.find(o.id, NestedReferenceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.sub, is(notNullValue())); - assertThat(document.sub.entities, is(notNullValue())); - assertThat(document.sub.entities.size(), is(1)); - assertThat(document.sub.entities.iterator().next().id, is(e.id)); - } - - @Edge - public static class BasicEdgeTestEntity extends BasicTestEntity { - @From - BasicTestEntity from; - @To - BasicTestEntity to; - - public BasicEdgeTestEntity() { - super(); - } - - public BasicEdgeTestEntity(final BasicTestEntity from, final BasicTestEntity to) { - super(); - this.from = from; - this.to = to; - } - - public BasicTestEntity getFrom() { - return from; - } - - public void setFrom(final BasicTestEntity from) { - this.from = from; - } - - public BasicTestEntity getTo() { - return to; - } - - public void setTo(final BasicTestEntity to) { - this.to = to; - } - - } - - @Test - public void edgeFromTo() { - final BasicTestEntity e1 = new BasicTestEntity(); - template.insert(e1); - final BasicTestEntity e2 = new BasicTestEntity(); - template.insert(e2); - final BasicEdgeTestEntity e0 = new BasicEdgeTestEntity(e1, e2); - template.insert(e0); - final BasicEdgeTestEntity document = template.find(e0.id, BasicEdgeTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.getFrom(), is(notNullValue())); - assertThat(document.getFrom().getId(), is(e1.getId())); - assertThat(document.getTo(), is(notNullValue())); - assertThat(document.getTo().getId(), is(e2.getId())); - } - - @Edge - public static class BasicEdgeLazyTestEntity extends BasicTestEntity { - @From(lazy = true) - BasicTestEntity from; - @To(lazy = true) - BasicTestEntity to; - - public BasicEdgeLazyTestEntity() { - super(); - } - - public BasicEdgeLazyTestEntity(final BasicTestEntity from, final BasicTestEntity to) { - super(); - this.from = from; - this.to = to; - } - - public BasicTestEntity getFrom() { - return from; - } - - public void setFrom(final BasicTestEntity from) { - this.from = from; - } - - public BasicTestEntity getTo() { - return to; - } - - public void setTo(final BasicTestEntity to) { - this.to = to; - } - - } - - @Test - public void edgeFromToLazy() { - final BasicTestEntity e1 = new BasicTestEntity(); - template.insert(e1); - final BasicTestEntity e2 = new BasicTestEntity(); - template.insert(e2); - final BasicEdgeLazyTestEntity e0 = new BasicEdgeLazyTestEntity(e1, e2); - template.insert(e0); - final BasicEdgeLazyTestEntity document = template.find(e0.id, BasicEdgeLazyTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.getFrom(), is(notNullValue())); - assertThat(document.getFrom().getId(), is(e1.getId())); - assertThat(document.getTo(), is(notNullValue())); - assertThat(document.getTo().getId(), is(e2.getId())); - } - - public static class DocumentFromTestEntity extends BasicTestEntity { - @From - private Collection entities; - } - - @Test - public void documentFrom() { - final DocumentFromTestEntity e0 = new DocumentFromTestEntity(); - template.insert(e0); - final DocumentFromTestEntity e1 = new DocumentFromTestEntity(); - template.insert(e1); - final BasicEdgeLazyTestEntity edge0 = new BasicEdgeLazyTestEntity(e0, e1); - template.insert(edge0); - final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(e0, e1); - template.insert(edge1); - final DocumentFromTestEntity document = template.find(e0.id, DocumentFromTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - assertThat(document.entities.size(), is(2)); - for (final BasicEdgeLazyTestEntity e : document.entities) { - assertThat(e, instanceOf(BasicEdgeLazyTestEntity.class)); - assertThat(e.getId(), is(notNullValue())); - assertThat(e.getId(), is(isOneOf(edge0.getId(), edge1.getId()))); - assertThat(e.getFrom(), is(notNullValue())); - assertThat(e.getFrom().getId(), is(notNullValue())); - assertThat(e.getFrom().getId(), is(e0.getId())); - } - } - - public static class DocumentFromLazyTestEntity extends BasicTestEntity { - @From(lazy = true) - private Collection entities; - } - - @Test - public void documentFromLazy() { - final DocumentFromLazyTestEntity e0 = new DocumentFromLazyTestEntity(); - template.insert(e0); - final DocumentFromLazyTestEntity e1 = new DocumentFromLazyTestEntity(); - template.insert(e1); - final BasicEdgeLazyTestEntity edge0 = new BasicEdgeLazyTestEntity(e0, e1); - template.insert(edge0); - final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(e0, e1); - template.insert(edge1); - final DocumentFromLazyTestEntity document = template.find(e0.id, DocumentFromLazyTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - assertThat(document.entities.size(), is(2)); - for (final BasicEdgeLazyTestEntity e : document.entities) { - assertThat(e, instanceOf(BasicEdgeLazyTestEntity.class)); - assertThat(e.getId(), is(notNullValue())); - assertThat(e.getId(), is(isOneOf(edge0.getId(), edge1.getId()))); - assertThat(e.getFrom(), is(notNullValue())); - assertThat(e.getFrom().getId(), is(notNullValue())); - assertThat(e.getFrom().getId(), is(e0.getId())); - } - } - - public static class DocumentToTestEntity extends BasicTestEntity { - @To - private Collection entities; - } - - @Test - public void documentTo() { - final DocumentToTestEntity e0 = new DocumentToTestEntity(); - template.insert(e0); - final DocumentToTestEntity e1 = new DocumentToTestEntity(); - template.insert(e1); - final BasicEdgeLazyTestEntity edge0 = new BasicEdgeLazyTestEntity(e1, e0); - template.insert(edge0); - final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(e1, e0); - template.insert(edge1); - final DocumentToTestEntity document = template.find(e0.id, DocumentToTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - assertThat(document.entities.size(), is(2)); - for (final BasicEdgeLazyTestEntity e : document.entities) { - assertThat(e, instanceOf(BasicEdgeLazyTestEntity.class)); - assertThat(e.getId(), is(notNullValue())); - assertThat(e.getId(), is(isOneOf(edge0.getId(), edge1.getId()))); - assertThat(e.getTo(), is(notNullValue())); - assertThat(e.getTo().getId(), is(notNullValue())); - assertThat(e.getTo().getId(), is(e0.getId())); - } - } - - public static class DocumentToLazyTestEntity extends BasicTestEntity { - @To(lazy = true) - private Collection entities; - } - - @Test - public void documentToLazy() { - final DocumentToLazyTestEntity e0 = new DocumentToLazyTestEntity(); - template.insert(e0); - final DocumentToLazyTestEntity e1 = new DocumentToLazyTestEntity(); - template.insert(e1); - final BasicEdgeLazyTestEntity edge0 = new BasicEdgeLazyTestEntity(e1, e0); - template.insert(edge0); - final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(e1, e0); - template.insert(edge1); - final DocumentToLazyTestEntity document = template.find(e0.id, DocumentToLazyTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - assertThat(document.entities.size(), is(2)); - for (final BasicEdgeLazyTestEntity e : document.entities) { - assertThat(e, instanceOf(BasicEdgeLazyTestEntity.class)); - assertThat(e.getId(), is(notNullValue())); - assertThat(e.getId(), is(isOneOf(edge0.getId(), edge1.getId()))); - assertThat(e.getTo(), is(notNullValue())); - assertThat(e.getTo().getId(), is(notNullValue())); - assertThat(e.getTo().getId(), is(e0.getId())); - } - } - - public static class RelationsTestEntity extends BasicTestEntity { - @Relations(edges = BasicEdgeTestEntity.class) - private Collection entities; - } - - @Test - public void relations() { - final BasicTestEntity e1 = new BasicTestEntity(); - template.insert(e1); - final BasicTestEntity e2 = new BasicTestEntity(); - template.insert(e2); - final RelationsTestEntity e0 = new RelationsTestEntity(); - template.insert(e0); - template.insert(new BasicEdgeTestEntity(e0, e1)); - template.insert(new BasicEdgeTestEntity(e0, e2)); - - final RelationsTestEntity document = template.find(e0.id, RelationsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - assertThat(document.entities.size(), is(2)); - for (final BasicTestEntity e : document.entities) { - assertThat(e, instanceOf(BasicTestEntity.class)); - assertThat(e.getId(), is(notNullValue())); - assertThat(e.getId(), is(isOneOf(e1.getId(), e2.getId()))); - } - } - - public static class RelationsLazyTestEntity extends BasicTestEntity { - @Relations(edges = BasicEdgeTestEntity.class, lazy = true) - private Collection entities; - } - - @Test - public void relationsLazy() { - final BasicTestEntity e1 = new BasicTestEntity(); - template.insert(e1); - final BasicTestEntity e2 = new BasicTestEntity(); - template.insert(e2); - final RelationsTestEntity e0 = new RelationsTestEntity(); - template.insert(e0); - template.insert(new BasicEdgeTestEntity(e0, e1)); - template.insert(new BasicEdgeTestEntity(e0, e2)); - - final RelationsLazyTestEntity document = template.find(e0.id, RelationsLazyTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.entities, is(notNullValue())); - assertThat(document.entities.size(), is(2)); - for (final BasicTestEntity e : document.entities) { - assertThat(e, instanceOf(BasicTestEntity.class)); - assertThat(e.getId(), is(notNullValue())); - assertThat(e.getId(), is(isOneOf(e1.getId(), e2.getId()))); - } - } - - public static class ConstructorWithParamTestEntity extends BasicTestEntity { - private final String value; - - public ConstructorWithParamTestEntity(final String value) { - super(); - this.value = value; - } - } - - @Test - public void constructorWithParam() { - final ConstructorWithParamTestEntity entity = new ConstructorWithParamTestEntity("test"); - template.insert(entity); - final ConstructorWithParamTestEntity document = template - .find(entity.getId(), ConstructorWithParamTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(entity.value)); - } - - public static class ConstructorWithMultipleParamsTestEntity extends BasicTestEntity { - private final String value1; - private final boolean value2; - private final double value3; - private final long value4; - private final int value5; - private final String[] value6; - - public ConstructorWithMultipleParamsTestEntity(final String value1, final boolean value2, final double value3, - final long value4, final int value5, final String[] value6) { - super(); - this.value1 = value1; - this.value2 = value2; - this.value3 = value3; - this.value4 = value4; - this.value5 = value5; - this.value6 = value6; - } - - } - - @Test - public void constructorWithMultipleParams() { - final ConstructorWithMultipleParamsTestEntity entity = new ConstructorWithMultipleParamsTestEntity("test", true, - 3.5, 13L, 69, new String[] { "a", "b" }); - template.insert(entity); - final ConstructorWithMultipleParamsTestEntity document = template - .find(entity.getId(), ConstructorWithMultipleParamsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value1, is(entity.value1)); - assertThat(document.value2, is(entity.value2)); - assertThat(document.value3, is(entity.value3)); - assertThat(document.value4, is(entity.value4)); - assertThat(document.value5, is(entity.value5)); - assertThat(document.value6, is(entity.value6)); - } - - public static class ConstructorWithRefParamsTestEntity extends BasicTestEntity { - @Ref - private final BasicTestEntity value1; - @Ref - private final Collection value2; - - public ConstructorWithRefParamsTestEntity(final BasicTestEntity value1, - final Collection value2) { - super(); - this.value1 = value1; - this.value2 = value2; - } - } - - @Test - public void constructorWithRefParams() { - final BasicTestEntity value1 = new BasicTestEntity(); - final BasicTestEntity value2 = new BasicTestEntity(); - final BasicTestEntity value3 = new BasicTestEntity(); - template.insert(value1); - template.insert(value2); - template.insert(value3); - final ConstructorWithRefParamsTestEntity entity = new ConstructorWithRefParamsTestEntity(value1, - Arrays.asList(value2, value3)); - template.insert(entity); - final ConstructorWithRefParamsTestEntity document = template - .find(entity.id, ConstructorWithRefParamsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value1.id, is(value1.id)); - assertThat(document.value2.size(), is(2)); - assertThat(document.value2.stream().map((e) -> e.id).collect(Collectors.toList()), - hasItems(value2.id, value3.id)); - } - - public static class ConstructorWithRefLazyParamsTestEntity extends BasicTestEntity { - @Ref(lazy = true) - private final BasicTestEntity value1; - @Ref(lazy = true) - private final Collection value2; - - public ConstructorWithRefLazyParamsTestEntity(final BasicTestEntity value1, - final Collection value2) { - super(); - this.value1 = value1; - this.value2 = value2; - } - } - - @Test - public void constructorWithRefLazyParams() { - final BasicTestEntity value1 = new BasicTestEntity(); - final BasicTestEntity value2 = new BasicTestEntity(); - final BasicTestEntity value3 = new BasicTestEntity(); - template.insert(value1); - template.insert(value2); - template.insert(value3); - final ConstructorWithRefLazyParamsTestEntity entity = new ConstructorWithRefLazyParamsTestEntity(value1, - Arrays.asList(value2, value3)); - template.insert(entity); - final ConstructorWithRefLazyParamsTestEntity document = template - .find(entity.id, ConstructorWithRefLazyParamsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value1.getId(), is(value1.id)); - assertThat(document.value2.size(), is(2)); - assertThat(document.value2.stream().map((e) -> e.getId()).collect(Collectors.toList()), - hasItems(value2.id, value3.id)); - } - - public static class ConstructorWithRelationsParamsTestEntity extends BasicTestEntity { - @Relations(edges = BasicEdgeTestEntity.class) - private final Collection value; - - public ConstructorWithRelationsParamsTestEntity(final Collection value) { - super(); - this.value = value; - } - } - - @Test - public void constructorWithRelationsParams() { - final BasicTestEntity vertex1 = new BasicTestEntity(); - final BasicTestEntity vertex2 = new BasicTestEntity(); - template.insert(vertex1); - template.insert(vertex2); - final ConstructorWithRelationsParamsTestEntity entity = new ConstructorWithRelationsParamsTestEntity( - Arrays.asList(vertex1, vertex2)); - template.insert(entity); - template.insert(new BasicEdgeTestEntity(entity, vertex1)); - template.insert(new BasicEdgeTestEntity(entity, vertex2)); - final ConstructorWithRelationsParamsTestEntity document = template - .find(entity.id, ConstructorWithRelationsParamsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), - hasItems(vertex1.id, vertex2.id)); - } - - public static class ConstructorWithRelationsLazyParamsTestEntity extends BasicTestEntity { - @Relations(edges = BasicEdgeTestEntity.class, lazy = true) - private final Collection value; - - public ConstructorWithRelationsLazyParamsTestEntity(final Collection value) { - super(); - this.value = value; - } - } - - @Test - public void constructorWithRelationsLazyParams() { - final BasicTestEntity vertex1 = new BasicTestEntity(); - final BasicTestEntity vertex2 = new BasicTestEntity(); - template.insert(vertex1); - template.insert(vertex2); - final ConstructorWithRelationsLazyParamsTestEntity entity = new ConstructorWithRelationsLazyParamsTestEntity( - Arrays.asList(vertex1, vertex2)); - template.insert(entity); - template.insert(new BasicEdgeTestEntity(entity, vertex1)); - template.insert(new BasicEdgeTestEntity(entity, vertex2)); - final ConstructorWithRelationsLazyParamsTestEntity document = template - .find(entity.id, ConstructorWithRelationsLazyParamsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), - hasItems(vertex1.id, vertex2.id)); - } - - public static class ConstructorWithFromParamsTestEntity extends BasicTestEntity { - @From - private final Collection value; - - public ConstructorWithFromParamsTestEntity(final Collection value) { - super(); - this.value = value; - } - } - - @Test - public void constructorWithFromParams() { - final ConstructorWithFromParamsTestEntity entity = new ConstructorWithFromParamsTestEntity(null); - template.insert(entity); - final BasicTestEntity to = new BasicTestEntity(); - template.insert(to); - final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(entity, to); - final BasicEdgeLazyTestEntity edge2 = new BasicEdgeLazyTestEntity(entity, to); - template.insert(edge1); - template.insert(edge2); - final ConstructorWithFromParamsTestEntity document = template - .find(entity.id, ConstructorWithFromParamsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), hasItems(edge1.id, edge2.id)); - } - - public static class ConstructorWithFromLazyParamsTestEntity extends BasicTestEntity { - @From(lazy = true) - private final Collection value; - - public ConstructorWithFromLazyParamsTestEntity(final Collection value) { - super(); - this.value = value; - } - } - - @Test - public void constructorWithFromLazyParams() { - final ConstructorWithFromLazyParamsTestEntity entity = new ConstructorWithFromLazyParamsTestEntity(null); - template.insert(entity); - final BasicTestEntity to = new BasicTestEntity(); - template.insert(to); - final BasicEdgeTestEntity edge1 = new BasicEdgeTestEntity(entity, to); - final BasicEdgeTestEntity edge2 = new BasicEdgeTestEntity(entity, to); - template.insert(edge1); - template.insert(edge2); - final ConstructorWithFromLazyParamsTestEntity document = template - .find(entity.id, ConstructorWithFromLazyParamsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value.stream().map((e) -> e.getId()).collect(Collectors.toList()), - hasItems(edge1.id, edge2.id)); - } - - public static class ConstructorWithToParamsTestEntity extends BasicTestEntity { - @To - private final Collection value; - - public ConstructorWithToParamsTestEntity(final Collection value) { - super(); - this.value = value; - } - } - - @Test - public void constructorWithToParams() { - final ConstructorWithToParamsTestEntity entity = new ConstructorWithToParamsTestEntity(null); - template.insert(entity); - final BasicTestEntity from = new BasicTestEntity(); - template.insert(from); - final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(from, entity); - final BasicEdgeLazyTestEntity edge2 = new BasicEdgeLazyTestEntity(from, entity); - template.insert(edge1); - template.insert(edge2); - final ConstructorWithToParamsTestEntity document = template - .find(entity.id, ConstructorWithToParamsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), hasItems(edge1.id, edge2.id)); - } - - public static class ConstructorWithToLazyParamsTestEntity extends BasicTestEntity { - @To(lazy = true) - private final Collection value; - - public ConstructorWithToLazyParamsTestEntity(final Collection value) { - super(); - this.value = value; - } - } - - @Test - public void constructorWithToLazyParams() { - final ConstructorWithToLazyParamsTestEntity entity = new ConstructorWithToLazyParamsTestEntity(null); - template.insert(entity); - final BasicTestEntity from = new BasicTestEntity(); - template.insert(from); - final BasicEdgeTestEntity edge1 = new BasicEdgeTestEntity(from, entity); - final BasicEdgeTestEntity edge2 = new BasicEdgeTestEntity(from, entity); - template.insert(edge1); - template.insert(edge2); - final ConstructorWithToLazyParamsTestEntity document = template - .find(entity.id, ConstructorWithToLazyParamsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value.stream().map((e) -> e.getId()).collect(Collectors.toList()), - hasItems(edge1.id, edge2.id)); - } - - public static class EdgeConstructorWithFromToParamsTestEntity extends BasicEdgeTestEntity { - @From - private final BasicTestEntity from; - @To - private final BasicTestEntity to; - - public EdgeConstructorWithFromToParamsTestEntity(final BasicTestEntity from, final BasicTestEntity to) { - super(); - this.from = from; - this.to = to; - } - } - - @Test - public void edgeConstructorWithFromToParams() { - final BasicTestEntity from = new BasicTestEntity(); - final BasicTestEntity to = new BasicTestEntity(); - template.insert(from); - template.insert(to); - final EdgeConstructorWithFromToParamsTestEntity edge = new EdgeConstructorWithFromToParamsTestEntity(from, to); - template.insert(edge); - final EdgeConstructorWithFromToParamsTestEntity document = template - .find(edge.id, EdgeConstructorWithFromToParamsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.from.id, is(from.id)); - assertThat(document.to.id, is(to.id)); - } - - public static class EdgeConstructorWithFromToLazyParamsTestEntity extends BasicEdgeTestEntity { - @From - private final BasicTestEntity from; - @To - private final BasicTestEntity to; - - public EdgeConstructorWithFromToLazyParamsTestEntity(final BasicTestEntity from, final BasicTestEntity to) { - super(); - this.from = from; - this.to = to; - } - } - - @Test - public void edgeConstructorWithFromToLazyParams() { - final BasicTestEntity from = new BasicTestEntity(); - final BasicTestEntity to = new BasicTestEntity(); - template.insert(from); - template.insert(to); - final EdgeConstructorWithFromToLazyParamsTestEntity edge = new EdgeConstructorWithFromToLazyParamsTestEntity( - from, to); - template.insert(edge); - final EdgeConstructorWithFromToLazyParamsTestEntity document = template - .find(edge.id, EdgeConstructorWithFromToLazyParamsTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.from.getId(), is(from.id)); - assertThat(document.to.getId(), is(to.id)); - } - - public static class JodaTestEntity extends BasicTestEntity { - private org.joda.time.DateTime value1; - private org.joda.time.Instant value2; - private org.joda.time.LocalDate value3; - private org.joda.time.LocalDateTime value4; - } - - @Test - public void jodaMapping() { - final JodaTestEntity entity = new JodaTestEntity(); - entity.value1 = org.joda.time.DateTime.now(DateTimeZone.forOffsetHours(1)); - entity.value2 = org.joda.time.Instant.now(); - entity.value3 = org.joda.time.LocalDate.now(); - entity.value4 = org.joda.time.LocalDateTime.now(); - template.insert(entity); - final JodaTestEntity document = template.find(entity.getId(), JodaTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value1, is(entity.value1)); - assertThat(document.value2, is(entity.value2)); - assertThat(document.value3, is(entity.value3)); - assertThat(document.value4, is(entity.value4)); - } - - public static class Java8TimeTestEntity extends BasicTestEntity { - private java.time.Instant value1; - private java.time.LocalDate value2; - private java.time.LocalDateTime value3; - } - - @Test - public void timeMapping() { - final Java8TimeTestEntity entity = new Java8TimeTestEntity(); - entity.value1 = java.time.Instant.now(); - entity.value2 = java.time.LocalDate.now(); - entity.value3 = java.time.LocalDateTime.now(); - template.insert(entity); - final Java8TimeTestEntity document = template.find(entity.getId(), Java8TimeTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value1, is(entity.value1)); - assertThat(document.value2, is(entity.value2)); - assertThat(document.value3, is(entity.value3)); - } - - public static class SimpleBasicChildTestEntity extends BasicTestEntity { - private String field; - } - - public static class ComplexBasicChildTestEntity extends BasicTestEntity { - private BasicTestEntity nestedEntity; - } - - public static class PropertyInheritanceTestEntity extends BasicTestEntity { - private BasicTestEntity value; - } - - @Test - public void simplePropertyInheritanceMapping() { - final SimpleBasicChildTestEntity child = new SimpleBasicChildTestEntity(); - child.field = "value"; - final PropertyInheritanceTestEntity entity = new PropertyInheritanceTestEntity(); - entity.value = child; - template.insert(entity); - final PropertyInheritanceTestEntity document = template - .find(entity.getId(), PropertyInheritanceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(instanceOf(SimpleBasicChildTestEntity.class))); - assertThat(((SimpleBasicChildTestEntity) document.value).field, is(child.field)); - } - - @Test - public void complexPropertyInheritanceMapping() { - final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); - innerChild.field = "value"; - final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); - child.nestedEntity = innerChild; - final PropertyInheritanceTestEntity entity = new PropertyInheritanceTestEntity(); - entity.value = child; - template.insert(entity); - final PropertyInheritanceTestEntity document = template - .find(entity.getId(), PropertyInheritanceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); - final ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; - assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - final SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; - assertThat(simpleDocument.field, is(innerChild.field)); - } - - public static class ListInheritanceTestEntity extends BasicTestEntity { - private List value; - } - - @Test - public void simpleListInheritanceMapping() { - final List list = new ArrayList<>(); - final String value = "value"; - for (int i = 0; i < 3; ++i) { - final SimpleBasicChildTestEntity child = new SimpleBasicChildTestEntity(); - child.field = value; - list.add(child); - } - final ListInheritanceTestEntity entity = new ListInheritanceTestEntity(); - entity.value = list; - template.insert(entity); - final ListInheritanceTestEntity document = template.find(entity.getId(), ListInheritanceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(instanceOf(List.class))); - for (final BasicTestEntity elem : document.value) { - assertThat(elem, is(instanceOf(SimpleBasicChildTestEntity.class))); - assertThat(((SimpleBasicChildTestEntity) elem).field, is(value)); - } - } - - @Test - public void complexListInheritanceMapping() { - final List list = new ArrayList<>(); - final String value = "value"; - for (int i = 0; i < 3; ++i) { - final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); - innerChild.field = value; - final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); - child.nestedEntity = innerChild; - list.add(child); - } - final ListInheritanceTestEntity entity = new ListInheritanceTestEntity(); - entity.value = list; - template.insert(entity); - final ListInheritanceTestEntity document = template.find(entity.getId(), ListInheritanceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(instanceOf(List.class))); - for (final BasicTestEntity elem : document.value) { - assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); - final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; - assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; - assertThat(simpleElem.field, is(value)); - } - } - - @SuppressWarnings("rawtypes") - public static class UntypedListInheritanceTestEntity extends BasicTestEntity { - private List value; - } - - @Test - public void untypedListInheritanceMapping() { - final List list = new ArrayList<>(); - final String value = "value"; - for (int i = 0; i < 3; ++i) { - final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); - innerChild.field = value; - final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); - child.nestedEntity = innerChild; - list.add(child); - } - final UntypedListInheritanceTestEntity entity = new UntypedListInheritanceTestEntity(); - entity.value = list; - template.insert(entity); - final UntypedListInheritanceTestEntity document = template - .find(entity.getId(), UntypedListInheritanceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(instanceOf(List.class))); - for (final Object elem : document.value) { - assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); - final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; - assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; - assertThat(simpleElem.field, is(value)); - } - } - - public static class MapInheritanceTestEntity extends BasicTestEntity { - private Map value; - } - - @Test - public void simpleMapInheritanceMapping() { - final Map map = new HashMap<>(); - final String value = "value"; - for (int i = 0; i < 3; ++i) { - final SimpleBasicChildTestEntity child = new SimpleBasicChildTestEntity(); - child.field = value; - map.put(String.valueOf(i), child); - } - final MapInheritanceTestEntity entity = new MapInheritanceTestEntity(); - entity.value = map; - template.insert(entity); - final MapInheritanceTestEntity document = template.find(entity.getId(), MapInheritanceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(instanceOf(Map.class))); - for (final Map.Entry entry : document.value.entrySet()) { - assertThat(entry.getValue(), is(instanceOf(SimpleBasicChildTestEntity.class))); - assertThat(((SimpleBasicChildTestEntity) entry.getValue()).field, is(value)); - } - } - - @Test - public void complexMapInheritanceMapping() { - final Map map = new HashMap<>(); - final String value = "value"; - for (int i = 0; i < 3; ++i) { - final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); - innerChild.field = value; - final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); - child.nestedEntity = innerChild; - map.put(String.valueOf(i), child); - } - final MapInheritanceTestEntity entity = new MapInheritanceTestEntity(); - entity.value = map; - template.insert(entity); - final MapInheritanceTestEntity document = template.find(entity.getId(), MapInheritanceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(instanceOf(Map.class))); - for (final Map.Entry entry : document.value.entrySet()) { - assertThat(entry.getValue(), is(instanceOf(ComplexBasicChildTestEntity.class))); - final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) entry.getValue(); - assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; - assertThat(simpleElem.field, is(value)); - } - } - - @SuppressWarnings("rawtypes") - public static class UntypedMapInheritanceTestEntity extends BasicTestEntity { - private Map value; - } - - @SuppressWarnings("rawtypes") - @Test - public void untypedMapInheritanceMapping() { - final Map map = new HashMap<>(); - final String value = "value"; - for (int i = 0; i < 3; ++i) { - final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); - innerChild.field = value; - final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); - child.nestedEntity = innerChild; - map.put(String.valueOf(i), child); - } - final UntypedMapInheritanceTestEntity entity = new UntypedMapInheritanceTestEntity(); - entity.value = map; - template.insert(entity); - final UntypedMapInheritanceTestEntity document = template - .find(entity.getId(), UntypedMapInheritanceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(instanceOf(Map.class))); - for (final Object entry : document.value.entrySet()) { - final Object val = ((Map.Entry) entry).getValue(); - assertThat(val, is(instanceOf(ComplexBasicChildTestEntity.class))); - final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) val; - assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; - assertThat(simpleElem.field, is(value)); - } - } - - public static class ConstructorWithPropertyInheritanceTestEntity extends BasicTestEntity { - private final BasicTestEntity value; - - public ConstructorWithPropertyInheritanceTestEntity(final BasicTestEntity value) { - this.value = value; - } - } - - @Test - public void constructorPropertyInheritanceMapping() { - final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); - innerChild.field = "value"; - final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); - child.nestedEntity = innerChild; - final ConstructorWithPropertyInheritanceTestEntity entity = new ConstructorWithPropertyInheritanceTestEntity( - child); - template.insert(entity); - final ConstructorWithPropertyInheritanceTestEntity document = template - .find(entity.getId(), ConstructorWithPropertyInheritanceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); - final ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; - assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - final SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; - assertThat(simpleDocument.field, is(innerChild.field)); - } - - public static class ListInMapInheritanceTestEntity extends BasicTestEntity { - private Map> value; - } - - @Test - public void listInMapInheritanceMapping() { - final Map> map = new HashMap<>(); - final String value = "value"; - for (int i = 0; i < 3; ++i) { - final List list = new ArrayList<>(); - map.put(String.valueOf(i), list); - for (int j = 0; j < 3; ++j) { - final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); - innerChild.field = value; - final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); - child.nestedEntity = innerChild; - list.add(child); - } - } - final ListInMapInheritanceTestEntity entity = new ListInMapInheritanceTestEntity(); - entity.value = map; - template.insert(entity); - final ListInMapInheritanceTestEntity document = template - .find(entity.getId(), ListInMapInheritanceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(instanceOf(Map.class))); - for (final Map.Entry> entry : document.value.entrySet()) { - assertThat(entry.getValue(), is(instanceOf(List.class))); - for (final BasicTestEntity elem : entry.getValue()) { - assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); - final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; - assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; - assertThat(simpleElem.field, is(value)); - } - } - } - - public static class PropertyRefInheritanceTestEntity extends BasicTestEntity { - @Ref - private BasicTestEntity value; - } - - @Test - public void propertyRefInheritanceMapping() { - final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); - innerChild.field = "value"; - final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); - child.nestedEntity = innerChild; - final PropertyRefInheritanceTestEntity entity = new PropertyRefInheritanceTestEntity(); - entity.value = child; - template.insert(child); - template.insert(entity); - final PropertyRefInheritanceTestEntity document = template - .find(entity.getId(), PropertyRefInheritanceTestEntity.class).get(); - assertThat(document, is(notNullValue())); - assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); - final ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; - assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); - final SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; - assertThat(simpleDocument.field, is(innerChild.field)); - } - - public class SimpleTypesTestEntity extends BasicTestEntity { - private String stringValue; - private Boolean boolValue; - private int intValue; - private Long longValue; - private Short shortValue; - private Float floatValue; - private Double doubleValue; - private Character charValue; - private Byte byteValue; - private Date dateValue; - private java.sql.Date sqlDateValue; - private Timestamp timestampValue; - private byte[] byteArray; - } - - @Test - public void simpleTypesMapping() { - final SimpleTypesTestEntity entity = new SimpleTypesTestEntity(); - entity.stringValue = "hello world"; - entity.boolValue = true; - entity.intValue = 123456; - entity.longValue = 1234567890123456789l; - entity.shortValue = 1234; - entity.floatValue = 1.234567890f; - entity.doubleValue = 1.2345678901234567890; - entity.charValue = 'a'; - entity.byteValue = 'z'; - entity.dateValue = new Date(); - entity.sqlDateValue = new java.sql.Date(new Date().getTime()); - entity.timestampValue = new Timestamp(new Date().getTime()); - entity.byteArray = new byte[] { 'a', 'b', 'c', 'x', 'y', 'z' }; - template.insert(entity); - final SimpleTypesTestEntity document = template.find(entity.getId(), SimpleTypesTestEntity.class).get(); - assertThat(entity.stringValue, is(document.stringValue)); - assertThat(entity.boolValue, is(document.boolValue)); - assertThat(entity.intValue, is(document.intValue)); - assertThat(entity.longValue, is(document.longValue)); - assertThat(entity.shortValue, is(document.shortValue)); - assertThat(entity.floatValue, is(document.floatValue)); - assertThat(entity.doubleValue, is(document.doubleValue)); - assertThat(entity.charValue, is(document.charValue)); - assertThat(entity.byteValue, is(document.byteValue)); - assertThat(entity.dateValue, is(document.dateValue)); - assertThat(entity.sqlDateValue, is(document.sqlDateValue)); - assertThat(entity.timestampValue, is(document.timestampValue)); - assertThat(entity.byteArray, is(document.byteArray)); - } - - public enum TestEnum { - A, B; - } - - public class EnumTestEntity extends BasicTestEntity { - private TestEnum value; - } - - @Test - public void enumMapping() { - final EnumTestEntity entity = new EnumTestEntity(); - entity.value = TestEnum.A; - template.insert(entity); - final EnumTestEntity document = template.find(entity.getId(), EnumTestEntity.class).get(); - assertThat(entity.value, is(document.value)); - } -} +/* + * 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.core.mapping; + +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isOneOf; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.joda.time.DateTimeZone; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.data.annotation.Id; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.arangodb.entity.DocumentEntity; +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.AbstractArangoTest; +import com.arangodb.springframework.ArangoTestConfiguration; +import com.arangodb.springframework.annotation.Document; +import com.arangodb.springframework.annotation.Edge; +import com.arangodb.springframework.annotation.Field; +import com.arangodb.springframework.annotation.From; +import com.arangodb.springframework.annotation.Key; +import com.arangodb.springframework.annotation.Ref; +import com.arangodb.springframework.annotation.Relations; +import com.arangodb.springframework.annotation.Rev; +import com.arangodb.springframework.annotation.To; +import com.arangodb.util.MapBuilder; +import com.arangodb.velocypack.VPackSlice; + +/** + * @author Mark Vollmary + * @author Christian Lechner + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { ArangoTestConfiguration.class }) +public class ArangoMappingTest extends AbstractArangoTest { + + @Document + public static class BasicTestEntity { + + @Id + String id; + @Key + String key; + @Rev + String rev; + + public BasicTestEntity() { + super(); + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(final String key) { + this.key = key; + } + + public String getRev() { + return rev; + } + + public void setRev(final String rev) { + this.rev = rev; + } + + } + + @Test + public void idKeyRev() { + final DocumentEntity ref = template.insert(new BasicTestEntity()); + final BasicTestEntity entity = template.find(ref.getId(), BasicTestEntity.class).get(); + assertThat(entity, is(notNullValue())); + assertThat(entity.getId(), is(ref.getId())); + assertThat(entity.getKey(), is(ref.getKey())); + assertThat(entity.getRev(), is(ref.getRev())); + } + + public static class OnlyIdTestEntity { + @Id + private String id; + } + + @Test + public void supplementKey() { + final OnlyIdTestEntity value = new OnlyIdTestEntity(); + template.insert(value); + final List result = template + .query("RETURN @doc", new MapBuilder().put("doc", value).get(), new AqlQueryOptions(), BasicTestEntity.class) + .asListRemaining(); + assertThat(result.size(), is(1)); + assertThat(result.get(0).getId(), is(value.id)); + assertThat(result.get(0).getKey(), is(value.id.split("/")[1])); + assertThat(result.get(0).getRev(), is(nullValue())); + } + + public static class FieldNameTestEntity extends BasicTestEntity { + @Field("alt-test") + private String test; + } + + @Test + public void fieldNameAnnotation() { + final FieldNameTestEntity entity = new FieldNameTestEntity(); + entity.test = "1234"; + final DocumentEntity res = template.insert(entity); + final VPackSlice slice = template.driver().db(ArangoTestConfiguration.DB).getDocument(res.getId(), + VPackSlice.class); + assertThat(slice, is(notNullValue())); + assertThat(slice.get("alt-test").isString(), is(true)); + assertThat(slice.get("alt-test").getAsString(), is(entity.test)); + } + + public static class SingleNestedDocumentTestEntity extends BasicTestEntity { + private NestedDocumentTestEntity entity; + } + + public static class NestedDocumentTestEntity { + private String test; + + public NestedDocumentTestEntity() { + super(); + } + + public NestedDocumentTestEntity(final String test) { + super(); + this.test = test; + } + } + + @Test + public void singleNestedDocument() { + final SingleNestedDocumentTestEntity entity = new SingleNestedDocumentTestEntity(); + entity.entity = new NestedDocumentTestEntity("test"); + template.insert(entity); + final SingleNestedDocumentTestEntity document = template.find(entity.id, SingleNestedDocumentTestEntity.class) + .get(); + assertThat(document, is(notNullValue())); + assertThat(document.entity, is(notNullValue())); + assertThat(document.entity.test, is("test")); + } + + public static class MultipleNestedDocumentTestEntity extends BasicTestEntity { + private Collection entities; + } + + @Test + public void multipleNestedDocuments() { + final MultipleNestedDocumentTestEntity entity = new MultipleNestedDocumentTestEntity(); + entity.entities = new ArrayList<>(Arrays.asList(new NestedDocumentTestEntity("0"), + new NestedDocumentTestEntity("1"), new NestedDocumentTestEntity("2"))); + template.insert(entity); + final MultipleNestedDocumentTestEntity document = template.find(entity.id, MultipleNestedDocumentTestEntity.class) + .get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + assertThat(document.entities.size(), is(3)); + assertThat(document.entities.stream().map(e -> e.test).collect(Collectors.toList()), hasItems("0", "1", "2")); + } + + public static class MultipleNestedCollectionsTestEntity extends BasicTestEntity { + private Collection> entities; + } + + @Test + public void multipleNestedCollections() { + final MultipleNestedCollectionsTestEntity entity = new MultipleNestedCollectionsTestEntity(); + entity.entities = new ArrayList<>(Arrays.asList( + new ArrayList<>(Arrays.asList(new NestedDocumentTestEntity("00"), new NestedDocumentTestEntity("01"), + new NestedDocumentTestEntity("02"))), + new ArrayList<>(Arrays.asList(new NestedDocumentTestEntity("10"), new NestedDocumentTestEntity("11"), + new NestedDocumentTestEntity("12"))))); + template.insert(entity); + final MultipleNestedCollectionsTestEntity document = template + .find(entity.id, MultipleNestedCollectionsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + assertThat(document.entities.size(), is(2)); + final List> collect = document.entities.stream() + .map(c -> c.stream().map(cc -> cc.test).collect(Collectors.toList())).collect(Collectors.toList()); + for (int i = 0; i < collect.size(); i++) { + for (int j = 0; j < collect.get(i).size(); j++) { + assertThat(collect.get(i).get(j), is(i + "" + j)); + } + } + } + + public static class SingleNestedMapTestEntity extends BasicTestEntity { + private Map entities; + } + + @Test + public void singleNestedMap() { + final SingleNestedMapTestEntity entity = new SingleNestedMapTestEntity(); + entity.entities = new HashMap<>(); + entity.entities.put("0", new NestedDocumentTestEntity("0")); + entity.entities.put("1", new NestedDocumentTestEntity("1")); + entity.entities.put("2", new NestedDocumentTestEntity("2")); + template.insert(entity); + final SingleNestedMapTestEntity document = template.find(entity.id, SingleNestedMapTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + final Map collect = document.entities.entrySet().stream() + .map(e -> new String[] { e.getKey(), e.getValue().test }).collect(Collectors.toMap(k -> k[0], v -> v[1])); + for (int i = 0; i <= 2; i++) { + assertThat(collect.get(String.valueOf(i)), is(String.valueOf(i))); + } + } + + public static class MultipleNestedMapTestEntity extends BasicTestEntity { + private Map> entities; + } + + @Test + public void multipleNestedMaps() { + final MultipleNestedMapTestEntity entity = new MultipleNestedMapTestEntity(); + entity.entities = new HashMap<>(); + final Map m0 = new HashMap<>(); + m0.put("0", new NestedDocumentTestEntity("00")); + m0.put("1", new NestedDocumentTestEntity("01")); + m0.put("2", new NestedDocumentTestEntity("02")); + entity.entities.put("0", m0); + final Map m1 = new HashMap<>(); + m1.put("0", new NestedDocumentTestEntity("10")); + m1.put("1", new NestedDocumentTestEntity("11")); + m1.put("2", new NestedDocumentTestEntity("12")); + entity.entities.put("1", m1); + template.insert(entity); + final MultipleNestedMapTestEntity document = template.find(entity.id, MultipleNestedMapTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + for (int i = 0; i <= 1; i++) { + for (int j = 0; j <= 2; j++) { + assertThat(document.entities.get(String.valueOf(i)).get(String.valueOf(j)).test, is(i + "" + j)); + } + } + } + + public static class SingleReferenceTestEntity extends BasicTestEntity { + @Ref + private BasicTestEntity entity; + } + + @Test + public void singleRef() { + final BasicTestEntity e1 = new BasicTestEntity(); + template.insert(e1); + final SingleReferenceTestEntity e0 = new SingleReferenceTestEntity(); + e0.entity = e1; + template.insert(e0); + final SingleReferenceTestEntity document = template.find(e0.id, SingleReferenceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entity, is(notNullValue())); + assertThat(document.entity.id, is(e1.id)); + } + + public static class SingleReferenceLazyTestEntity extends BasicTestEntity { + @Ref(lazy = true) + private BasicTestEntity entity; + } + + @Test + public void singleRefLazy() { + final BasicTestEntity e1 = new BasicTestEntity(); + template.insert(e1); + final SingleReferenceLazyTestEntity e0 = new SingleReferenceLazyTestEntity(); + e0.entity = e1; + template.insert(e0); + final SingleReferenceLazyTestEntity document = template.find(e0.id, SingleReferenceLazyTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entity, is(notNullValue())); + assertThat(document.entity, instanceOf(BasicTestEntity.class)); + assertThat(document.entity.getId(), is(e1.getId())); + } + + public static class MultiReferenceTestEntity extends BasicTestEntity { + @Ref + private Collection entities; + } + + @Test + public void multiRef() { + final BasicTestEntity e1 = new BasicTestEntity(); + template.insert(e1); + final BasicTestEntity e2 = new BasicTestEntity(); + template.insert(e2); + final MultiReferenceTestEntity e0 = new MultiReferenceTestEntity(); + e0.entities = Arrays.asList(e1, e2); + template.insert(e0); + final MultiReferenceTestEntity document = template.find(e0.id, MultiReferenceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + assertThat(document.entities.size(), is(2)); + for (final BasicTestEntity e : document.entities) { + assertThat(e, instanceOf(BasicTestEntity.class)); + assertThat(e.getId(), is(notNullValue())); + assertThat(e.getId(), is(isOneOf(e1.getId(), e2.getId()))); + } + } + + public static class MultiReferenceLazyTestEntity extends BasicTestEntity { + @Ref(lazy = true) + private Collection entities; + } + + @Test + public void multiRefLazy() { + final BasicTestEntity e1 = new BasicTestEntity(); + template.insert(e1); + final BasicTestEntity e2 = new BasicTestEntity(); + template.insert(e2); + final MultiReferenceLazyTestEntity e0 = new MultiReferenceLazyTestEntity(); + e0.entities = Arrays.asList(e1, e2); + template.insert(e0); + final MultiReferenceLazyTestEntity document = template.find(e0.id, MultiReferenceLazyTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + assertThat(document.entities.size(), is(2)); + for (final BasicTestEntity e : document.entities) { + assertThat(e, instanceOf(BasicTestEntity.class)); + assertThat(e.getId(), is(notNullValue())); + assertThat(e.getId(), is(isOneOf(e1.getId(), e2.getId()))); + } + } + + public static class NestedReferenceTestEntity extends BasicTestEntity { + private NestedReferenceSubTestEntity sub; + } + + public static class NestedReferenceSubTestEntity { + @Ref + private Collection entities; + } + + @Test + public void testNestedRef() { + final NestedReferenceTestEntity o = new NestedReferenceTestEntity(); + o.sub = new NestedReferenceSubTestEntity(); + o.sub.entities = new ArrayList<>(); + final BasicTestEntity e = new BasicTestEntity(); + o.sub.entities.add(e); + template.insert(e); + template.insert(o); + final NestedReferenceTestEntity document = template.find(o.id, NestedReferenceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.sub, is(notNullValue())); + assertThat(document.sub.entities, is(notNullValue())); + assertThat(document.sub.entities.size(), is(1)); + assertThat(document.sub.entities.iterator().next().id, is(e.id)); + } + + @Edge + public static class BasicEdgeTestEntity extends BasicTestEntity { + @From + BasicTestEntity from; + @To + BasicTestEntity to; + + public BasicEdgeTestEntity() { + super(); + } + + public BasicEdgeTestEntity(final BasicTestEntity from, final BasicTestEntity to) { + super(); + this.from = from; + this.to = to; + } + + public BasicTestEntity getFrom() { + return from; + } + + public void setFrom(final BasicTestEntity from) { + this.from = from; + } + + public BasicTestEntity getTo() { + return to; + } + + public void setTo(final BasicTestEntity to) { + this.to = to; + } + + } + + @Test + public void edgeFromTo() { + final BasicTestEntity e1 = new BasicTestEntity(); + template.insert(e1); + final BasicTestEntity e2 = new BasicTestEntity(); + template.insert(e2); + final BasicEdgeTestEntity e0 = new BasicEdgeTestEntity(e1, e2); + template.insert(e0); + final BasicEdgeTestEntity document = template.find(e0.id, BasicEdgeTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.getFrom(), is(notNullValue())); + assertThat(document.getFrom().getId(), is(e1.getId())); + assertThat(document.getTo(), is(notNullValue())); + assertThat(document.getTo().getId(), is(e2.getId())); + } + + @Edge + public static class BasicEdgeLazyTestEntity extends BasicTestEntity { + @From(lazy = true) + BasicTestEntity from; + @To(lazy = true) + BasicTestEntity to; + + public BasicEdgeLazyTestEntity() { + super(); + } + + public BasicEdgeLazyTestEntity(final BasicTestEntity from, final BasicTestEntity to) { + super(); + this.from = from; + this.to = to; + } + + public BasicTestEntity getFrom() { + return from; + } + + public void setFrom(final BasicTestEntity from) { + this.from = from; + } + + public BasicTestEntity getTo() { + return to; + } + + public void setTo(final BasicTestEntity to) { + this.to = to; + } + + } + + @Test + public void edgeFromToLazy() { + final BasicTestEntity e1 = new BasicTestEntity(); + template.insert(e1); + final BasicTestEntity e2 = new BasicTestEntity(); + template.insert(e2); + final BasicEdgeLazyTestEntity e0 = new BasicEdgeLazyTestEntity(e1, e2); + template.insert(e0); + final BasicEdgeLazyTestEntity document = template.find(e0.id, BasicEdgeLazyTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.getFrom(), is(notNullValue())); + assertThat(document.getFrom().getId(), is(e1.getId())); + assertThat(document.getTo(), is(notNullValue())); + assertThat(document.getTo().getId(), is(e2.getId())); + } + + public static class DocumentFromTestEntity extends BasicTestEntity { + @From + private Collection entities; + } + + @Test + public void documentFrom() { + final DocumentFromTestEntity e0 = new DocumentFromTestEntity(); + template.insert(e0); + final DocumentFromTestEntity e1 = new DocumentFromTestEntity(); + template.insert(e1); + final BasicEdgeLazyTestEntity edge0 = new BasicEdgeLazyTestEntity(e0, e1); + template.insert(edge0); + final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(e0, e1); + template.insert(edge1); + final DocumentFromTestEntity document = template.find(e0.id, DocumentFromTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + assertThat(document.entities.size(), is(2)); + for (final BasicEdgeLazyTestEntity e : document.entities) { + assertThat(e, instanceOf(BasicEdgeLazyTestEntity.class)); + assertThat(e.getId(), is(notNullValue())); + assertThat(e.getId(), is(isOneOf(edge0.getId(), edge1.getId()))); + assertThat(e.getFrom(), is(notNullValue())); + assertThat(e.getFrom().getId(), is(notNullValue())); + assertThat(e.getFrom().getId(), is(e0.getId())); + } + } + + public static class DocumentFromLazyTestEntity extends BasicTestEntity { + @From(lazy = true) + private Collection entities; + } + + @Test + public void documentFromLazy() { + final DocumentFromLazyTestEntity e0 = new DocumentFromLazyTestEntity(); + template.insert(e0); + final DocumentFromLazyTestEntity e1 = new DocumentFromLazyTestEntity(); + template.insert(e1); + final BasicEdgeLazyTestEntity edge0 = new BasicEdgeLazyTestEntity(e0, e1); + template.insert(edge0); + final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(e0, e1); + template.insert(edge1); + final DocumentFromLazyTestEntity document = template.find(e0.id, DocumentFromLazyTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + assertThat(document.entities.size(), is(2)); + for (final BasicEdgeLazyTestEntity e : document.entities) { + assertThat(e, instanceOf(BasicEdgeLazyTestEntity.class)); + assertThat(e.getId(), is(notNullValue())); + assertThat(e.getId(), is(isOneOf(edge0.getId(), edge1.getId()))); + assertThat(e.getFrom(), is(notNullValue())); + assertThat(e.getFrom().getId(), is(notNullValue())); + assertThat(e.getFrom().getId(), is(e0.getId())); + } + } + + public static class DocumentToTestEntity extends BasicTestEntity { + @To + private Collection entities; + } + + @Test + public void documentTo() { + final DocumentToTestEntity e0 = new DocumentToTestEntity(); + template.insert(e0); + final DocumentToTestEntity e1 = new DocumentToTestEntity(); + template.insert(e1); + final BasicEdgeLazyTestEntity edge0 = new BasicEdgeLazyTestEntity(e1, e0); + template.insert(edge0); + final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(e1, e0); + template.insert(edge1); + final DocumentToTestEntity document = template.find(e0.id, DocumentToTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + assertThat(document.entities.size(), is(2)); + for (final BasicEdgeLazyTestEntity e : document.entities) { + assertThat(e, instanceOf(BasicEdgeLazyTestEntity.class)); + assertThat(e.getId(), is(notNullValue())); + assertThat(e.getId(), is(isOneOf(edge0.getId(), edge1.getId()))); + assertThat(e.getTo(), is(notNullValue())); + assertThat(e.getTo().getId(), is(notNullValue())); + assertThat(e.getTo().getId(), is(e0.getId())); + } + } + + public static class DocumentToLazyTestEntity extends BasicTestEntity { + @To(lazy = true) + private Collection entities; + } + + @Test + public void documentToLazy() { + final DocumentToLazyTestEntity e0 = new DocumentToLazyTestEntity(); + template.insert(e0); + final DocumentToLazyTestEntity e1 = new DocumentToLazyTestEntity(); + template.insert(e1); + final BasicEdgeLazyTestEntity edge0 = new BasicEdgeLazyTestEntity(e1, e0); + template.insert(edge0); + final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(e1, e0); + template.insert(edge1); + final DocumentToLazyTestEntity document = template.find(e0.id, DocumentToLazyTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + assertThat(document.entities.size(), is(2)); + for (final BasicEdgeLazyTestEntity e : document.entities) { + assertThat(e, instanceOf(BasicEdgeLazyTestEntity.class)); + assertThat(e.getId(), is(notNullValue())); + assertThat(e.getId(), is(isOneOf(edge0.getId(), edge1.getId()))); + assertThat(e.getTo(), is(notNullValue())); + assertThat(e.getTo().getId(), is(notNullValue())); + assertThat(e.getTo().getId(), is(e0.getId())); + } + } + + public static class RelationsTestEntity extends BasicTestEntity { + @Relations(edges = BasicEdgeTestEntity.class) + private Collection entities; + } + + @Test + public void relations() { + final BasicTestEntity e1 = new BasicTestEntity(); + template.insert(e1); + final BasicTestEntity e2 = new BasicTestEntity(); + template.insert(e2); + final RelationsTestEntity e0 = new RelationsTestEntity(); + template.insert(e0); + template.insert(new BasicEdgeTestEntity(e0, e1)); + template.insert(new BasicEdgeTestEntity(e0, e2)); + + final RelationsTestEntity document = template.find(e0.id, RelationsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + assertThat(document.entities.size(), is(2)); + for (final BasicTestEntity e : document.entities) { + assertThat(e, instanceOf(BasicTestEntity.class)); + assertThat(e.getId(), is(notNullValue())); + assertThat(e.getId(), is(isOneOf(e1.getId(), e2.getId()))); + } + } + + public static class RelationsLazyTestEntity extends BasicTestEntity { + @Relations(edges = BasicEdgeTestEntity.class, lazy = true) + private Collection entities; + } + + @Test + public void relationsLazy() { + final BasicTestEntity e1 = new BasicTestEntity(); + template.insert(e1); + final BasicTestEntity e2 = new BasicTestEntity(); + template.insert(e2); + final RelationsTestEntity e0 = new RelationsTestEntity(); + template.insert(e0); + template.insert(new BasicEdgeTestEntity(e0, e1)); + template.insert(new BasicEdgeTestEntity(e0, e2)); + + final RelationsLazyTestEntity document = template.find(e0.id, RelationsLazyTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.entities, is(notNullValue())); + assertThat(document.entities.size(), is(2)); + for (final BasicTestEntity e : document.entities) { + assertThat(e, instanceOf(BasicTestEntity.class)); + assertThat(e.getId(), is(notNullValue())); + assertThat(e.getId(), is(isOneOf(e1.getId(), e2.getId()))); + } + } + + public static class ConstructorWithParamTestEntity extends BasicTestEntity { + private final String value; + + public ConstructorWithParamTestEntity(final String value) { + super(); + this.value = value; + } + } + + @Test + public void constructorWithParam() { + final ConstructorWithParamTestEntity entity = new ConstructorWithParamTestEntity("test"); + template.insert(entity); + final ConstructorWithParamTestEntity document = template.find(entity.getId(), ConstructorWithParamTestEntity.class) + .get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(entity.value)); + } + + public static class ConstructorWithMultipleParamsTestEntity extends BasicTestEntity { + private final String value1; + private final boolean value2; + private final double value3; + private final long value4; + private final int value5; + private final String[] value6; + + public ConstructorWithMultipleParamsTestEntity(final String value1, final boolean value2, final double value3, + final long value4, final int value5, final String[] value6) { + super(); + this.value1 = value1; + this.value2 = value2; + this.value3 = value3; + this.value4 = value4; + this.value5 = value5; + this.value6 = value6; + } + + } + + @Test + public void constructorWithMultipleParams() { + final ConstructorWithMultipleParamsTestEntity entity = new ConstructorWithMultipleParamsTestEntity("test", true, + 3.5, 13L, 69, new String[] { "a", "b" }); + template.insert(entity); + final ConstructorWithMultipleParamsTestEntity document = template + .find(entity.getId(), ConstructorWithMultipleParamsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value1, is(entity.value1)); + assertThat(document.value2, is(entity.value2)); + assertThat(document.value3, is(entity.value3)); + assertThat(document.value4, is(entity.value4)); + assertThat(document.value5, is(entity.value5)); + assertThat(document.value6, is(entity.value6)); + } + + public static class ConstructorWithRefParamsTestEntity extends BasicTestEntity { + @Ref + private final BasicTestEntity value1; + @Ref + private final Collection value2; + + public ConstructorWithRefParamsTestEntity(final BasicTestEntity value1, final Collection value2) { + super(); + this.value1 = value1; + this.value2 = value2; + } + } + + @Test + public void constructorWithRefParams() { + final BasicTestEntity value1 = new BasicTestEntity(); + final BasicTestEntity value2 = new BasicTestEntity(); + final BasicTestEntity value3 = new BasicTestEntity(); + template.insert(value1); + template.insert(value2); + template.insert(value3); + final ConstructorWithRefParamsTestEntity entity = new ConstructorWithRefParamsTestEntity(value1, + Arrays.asList(value2, value3)); + template.insert(entity); + final ConstructorWithRefParamsTestEntity document = template + .find(entity.id, ConstructorWithRefParamsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value1.id, is(value1.id)); + assertThat(document.value2.size(), is(2)); + assertThat(document.value2.stream().map((e) -> e.id).collect(Collectors.toList()), hasItems(value2.id, value3.id)); + } + + public static class ConstructorWithRefLazyParamsTestEntity extends BasicTestEntity { + @Ref(lazy = true) + private final BasicTestEntity value1; + @Ref(lazy = true) + private final Collection value2; + + public ConstructorWithRefLazyParamsTestEntity(final BasicTestEntity value1, + final Collection value2) { + super(); + this.value1 = value1; + this.value2 = value2; + } + } + + @Test + public void constructorWithRefLazyParams() { + final BasicTestEntity value1 = new BasicTestEntity(); + final BasicTestEntity value2 = new BasicTestEntity(); + final BasicTestEntity value3 = new BasicTestEntity(); + template.insert(value1); + template.insert(value2); + template.insert(value3); + final ConstructorWithRefLazyParamsTestEntity entity = new ConstructorWithRefLazyParamsTestEntity(value1, + Arrays.asList(value2, value3)); + template.insert(entity); + final ConstructorWithRefLazyParamsTestEntity document = template + .find(entity.id, ConstructorWithRefLazyParamsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value1.getId(), is(value1.id)); + assertThat(document.value2.size(), is(2)); + assertThat(document.value2.stream().map((e) -> e.getId()).collect(Collectors.toList()), + hasItems(value2.id, value3.id)); + } + + public static class ConstructorWithRelationsParamsTestEntity extends BasicTestEntity { + @Relations(edges = BasicEdgeTestEntity.class) + private final Collection value; + + public ConstructorWithRelationsParamsTestEntity(final Collection value) { + super(); + this.value = value; + } + } + + @Test + public void constructorWithRelationsParams() { + final BasicTestEntity vertex1 = new BasicTestEntity(); + final BasicTestEntity vertex2 = new BasicTestEntity(); + template.insert(vertex1); + template.insert(vertex2); + final ConstructorWithRelationsParamsTestEntity entity = new ConstructorWithRelationsParamsTestEntity( + Arrays.asList(vertex1, vertex2)); + template.insert(entity); + template.insert(new BasicEdgeTestEntity(entity, vertex1)); + template.insert(new BasicEdgeTestEntity(entity, vertex2)); + final ConstructorWithRelationsParamsTestEntity document = template + .find(entity.id, ConstructorWithRelationsParamsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), hasItems(vertex1.id, vertex2.id)); + } + + public static class ConstructorWithRelationsLazyParamsTestEntity extends BasicTestEntity { + @Relations(edges = BasicEdgeTestEntity.class, lazy = true) + private final Collection value; + + public ConstructorWithRelationsLazyParamsTestEntity(final Collection value) { + super(); + this.value = value; + } + } + + @Test + public void constructorWithRelationsLazyParams() { + final BasicTestEntity vertex1 = new BasicTestEntity(); + final BasicTestEntity vertex2 = new BasicTestEntity(); + template.insert(vertex1); + template.insert(vertex2); + final ConstructorWithRelationsLazyParamsTestEntity entity = new ConstructorWithRelationsLazyParamsTestEntity( + Arrays.asList(vertex1, vertex2)); + template.insert(entity); + template.insert(new BasicEdgeTestEntity(entity, vertex1)); + template.insert(new BasicEdgeTestEntity(entity, vertex2)); + final ConstructorWithRelationsLazyParamsTestEntity document = template + .find(entity.id, ConstructorWithRelationsLazyParamsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), hasItems(vertex1.id, vertex2.id)); + } + + public static class ConstructorWithFromParamsTestEntity extends BasicTestEntity { + @From + private final Collection value; + + public ConstructorWithFromParamsTestEntity(final Collection value) { + super(); + this.value = value; + } + } + + @Test + public void constructorWithFromParams() { + final ConstructorWithFromParamsTestEntity entity = new ConstructorWithFromParamsTestEntity(null); + template.insert(entity); + final BasicTestEntity to = new BasicTestEntity(); + template.insert(to); + final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(entity, to); + final BasicEdgeLazyTestEntity edge2 = new BasicEdgeLazyTestEntity(entity, to); + template.insert(edge1); + template.insert(edge2); + final ConstructorWithFromParamsTestEntity document = template + .find(entity.id, ConstructorWithFromParamsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), hasItems(edge1.id, edge2.id)); + } + + public static class ConstructorWithFromLazyParamsTestEntity extends BasicTestEntity { + @From(lazy = true) + private final Collection value; + + public ConstructorWithFromLazyParamsTestEntity(final Collection value) { + super(); + this.value = value; + } + } + + @Test + public void constructorWithFromLazyParams() { + final ConstructorWithFromLazyParamsTestEntity entity = new ConstructorWithFromLazyParamsTestEntity(null); + template.insert(entity); + final BasicTestEntity to = new BasicTestEntity(); + template.insert(to); + final BasicEdgeTestEntity edge1 = new BasicEdgeTestEntity(entity, to); + final BasicEdgeTestEntity edge2 = new BasicEdgeTestEntity(entity, to); + template.insert(edge1); + template.insert(edge2); + final ConstructorWithFromLazyParamsTestEntity document = template + .find(entity.id, ConstructorWithFromLazyParamsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value.stream().map((e) -> e.getId()).collect(Collectors.toList()), + hasItems(edge1.id, edge2.id)); + } + + public static class ConstructorWithToParamsTestEntity extends BasicTestEntity { + @To + private final Collection value; + + public ConstructorWithToParamsTestEntity(final Collection value) { + super(); + this.value = value; + } + } + + @Test + public void constructorWithToParams() { + final ConstructorWithToParamsTestEntity entity = new ConstructorWithToParamsTestEntity(null); + template.insert(entity); + final BasicTestEntity from = new BasicTestEntity(); + template.insert(from); + final BasicEdgeLazyTestEntity edge1 = new BasicEdgeLazyTestEntity(from, entity); + final BasicEdgeLazyTestEntity edge2 = new BasicEdgeLazyTestEntity(from, entity); + template.insert(edge1); + template.insert(edge2); + final ConstructorWithToParamsTestEntity document = template.find(entity.id, ConstructorWithToParamsTestEntity.class) + .get(); + assertThat(document, is(notNullValue())); + assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), hasItems(edge1.id, edge2.id)); + } + + public static class ConstructorWithToLazyParamsTestEntity extends BasicTestEntity { + @To(lazy = true) + private final Collection value; + + public ConstructorWithToLazyParamsTestEntity(final Collection value) { + super(); + this.value = value; + } + } + + @Test + public void constructorWithToLazyParams() { + final ConstructorWithToLazyParamsTestEntity entity = new ConstructorWithToLazyParamsTestEntity(null); + template.insert(entity); + final BasicTestEntity from = new BasicTestEntity(); + template.insert(from); + final BasicEdgeTestEntity edge1 = new BasicEdgeTestEntity(from, entity); + final BasicEdgeTestEntity edge2 = new BasicEdgeTestEntity(from, entity); + template.insert(edge1); + template.insert(edge2); + final ConstructorWithToLazyParamsTestEntity document = template + .find(entity.id, ConstructorWithToLazyParamsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value.stream().map((e) -> e.getId()).collect(Collectors.toList()), + hasItems(edge1.id, edge2.id)); + } + + public static class EdgeConstructorWithFromToParamsTestEntity extends BasicEdgeTestEntity { + @From + private final BasicTestEntity from; + @To + private final BasicTestEntity to; + + public EdgeConstructorWithFromToParamsTestEntity(final BasicTestEntity from, final BasicTestEntity to) { + super(); + this.from = from; + this.to = to; + } + } + + @Test + public void edgeConstructorWithFromToParams() { + final BasicTestEntity from = new BasicTestEntity(); + final BasicTestEntity to = new BasicTestEntity(); + template.insert(from); + template.insert(to); + final EdgeConstructorWithFromToParamsTestEntity edge = new EdgeConstructorWithFromToParamsTestEntity(from, to); + template.insert(edge); + final EdgeConstructorWithFromToParamsTestEntity document = template + .find(edge.id, EdgeConstructorWithFromToParamsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.from.id, is(from.id)); + assertThat(document.to.id, is(to.id)); + } + + public static class EdgeConstructorWithFromToLazyParamsTestEntity extends BasicEdgeTestEntity { + @From + private final BasicTestEntity from; + @To + private final BasicTestEntity to; + + public EdgeConstructorWithFromToLazyParamsTestEntity(final BasicTestEntity from, final BasicTestEntity to) { + super(); + this.from = from; + this.to = to; + } + } + + @Test + public void edgeConstructorWithFromToLazyParams() { + final BasicTestEntity from = new BasicTestEntity(); + final BasicTestEntity to = new BasicTestEntity(); + template.insert(from); + template.insert(to); + final EdgeConstructorWithFromToLazyParamsTestEntity edge = new EdgeConstructorWithFromToLazyParamsTestEntity(from, + to); + template.insert(edge); + final EdgeConstructorWithFromToLazyParamsTestEntity document = template + .find(edge.id, EdgeConstructorWithFromToLazyParamsTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.from.getId(), is(from.id)); + assertThat(document.to.getId(), is(to.id)); + } + + public static class JodaTestEntity extends BasicTestEntity { + private org.joda.time.DateTime value1; + private org.joda.time.Instant value2; + private org.joda.time.LocalDate value3; + private org.joda.time.LocalDateTime value4; + } + + @Test + public void jodaMapping() { + final JodaTestEntity entity = new JodaTestEntity(); + entity.value1 = org.joda.time.DateTime.now(DateTimeZone.forOffsetHours(1)); + entity.value2 = org.joda.time.Instant.now(); + entity.value3 = org.joda.time.LocalDate.now(); + entity.value4 = org.joda.time.LocalDateTime.now(); + template.insert(entity); + final JodaTestEntity document = template.find(entity.getId(), JodaTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value1, is(entity.value1)); + assertThat(document.value2, is(entity.value2)); + assertThat(document.value3, is(entity.value3)); + assertThat(document.value4, is(entity.value4)); + } + + public static class Java8TimeTestEntity extends BasicTestEntity { + private java.time.Instant value1; + private java.time.LocalDate value2; + private java.time.LocalDateTime value3; + } + + @Test + public void timeMapping() { + final Java8TimeTestEntity entity = new Java8TimeTestEntity(); + entity.value1 = java.time.Instant.now(); + entity.value2 = java.time.LocalDate.now(); + entity.value3 = java.time.LocalDateTime.now(); + template.insert(entity); + final Java8TimeTestEntity document = template.find(entity.getId(), Java8TimeTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value1, is(entity.value1)); + assertThat(document.value2, is(entity.value2)); + assertThat(document.value3, is(entity.value3)); + } + + public static class SimpleBasicChildTestEntity extends BasicTestEntity { + private String field; + } + + public static class ComplexBasicChildTestEntity extends BasicTestEntity { + private BasicTestEntity nestedEntity; + } + + public static class PropertyInheritanceTestEntity extends BasicTestEntity { + private BasicTestEntity value; + } + + @Test + public void simplePropertyInheritanceMapping() { + final SimpleBasicChildTestEntity child = new SimpleBasicChildTestEntity(); + child.field = "value"; + final PropertyInheritanceTestEntity entity = new PropertyInheritanceTestEntity(); + entity.value = child; + template.insert(entity); + final PropertyInheritanceTestEntity document = template.find(entity.getId(), PropertyInheritanceTestEntity.class) + .get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(SimpleBasicChildTestEntity.class))); + assertThat(((SimpleBasicChildTestEntity) document.value).field, is(child.field)); + } + + @Test + public void complexPropertyInheritanceMapping() { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = "value"; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + final PropertyInheritanceTestEntity entity = new PropertyInheritanceTestEntity(); + entity.value = child; + template.insert(entity); + final PropertyInheritanceTestEntity document = template.find(entity.getId(), PropertyInheritanceTestEntity.class) + .get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); + final ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; + assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + final SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; + assertThat(simpleDocument.field, is(innerChild.field)); + } + + public static class ListInheritanceTestEntity extends BasicTestEntity { + private List value; + } + + @Test + public void simpleListInheritanceMapping() { + final List list = new ArrayList<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity child = new SimpleBasicChildTestEntity(); + child.field = value; + list.add(child); + } + final ListInheritanceTestEntity entity = new ListInheritanceTestEntity(); + entity.value = list; + template.insert(entity); + final ListInheritanceTestEntity document = template.find(entity.getId(), ListInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(List.class))); + for (final BasicTestEntity elem : document.value) { + assertThat(elem, is(instanceOf(SimpleBasicChildTestEntity.class))); + assertThat(((SimpleBasicChildTestEntity) elem).field, is(value)); + } + } + + @Test + public void complexListInheritanceMapping() { + final List list = new ArrayList<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = value; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + list.add(child); + } + final ListInheritanceTestEntity entity = new ListInheritanceTestEntity(); + entity.value = list; + template.insert(entity); + final ListInheritanceTestEntity document = template.find(entity.getId(), ListInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(List.class))); + for (final BasicTestEntity elem : document.value) { + assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); + final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; + assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + assertThat(simpleElem.field, is(value)); + } + } + + @SuppressWarnings("rawtypes") + public static class UntypedListInheritanceTestEntity extends BasicTestEntity { + private List value; + } + + @Test + public void untypedListInheritanceMapping() { + final List list = new ArrayList<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = value; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + list.add(child); + } + final UntypedListInheritanceTestEntity entity = new UntypedListInheritanceTestEntity(); + entity.value = list; + template.insert(entity); + final UntypedListInheritanceTestEntity document = template + .find(entity.getId(), UntypedListInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(List.class))); + for (final Object elem : document.value) { + assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); + final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; + assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + assertThat(simpleElem.field, is(value)); + } + } + + public static class MapInheritanceTestEntity extends BasicTestEntity { + private Map value; + } + + @Test + public void simpleMapInheritanceMapping() { + final Map map = new HashMap<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity child = new SimpleBasicChildTestEntity(); + child.field = value; + map.put(String.valueOf(i), child); + } + final MapInheritanceTestEntity entity = new MapInheritanceTestEntity(); + entity.value = map; + template.insert(entity); + final MapInheritanceTestEntity document = template.find(entity.getId(), MapInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(Map.class))); + for (final Map.Entry entry : document.value.entrySet()) { + assertThat(entry.getValue(), is(instanceOf(SimpleBasicChildTestEntity.class))); + assertThat(((SimpleBasicChildTestEntity) entry.getValue()).field, is(value)); + } + } + + @Test + public void complexMapInheritanceMapping() { + final Map map = new HashMap<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = value; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + map.put(String.valueOf(i), child); + } + final MapInheritanceTestEntity entity = new MapInheritanceTestEntity(); + entity.value = map; + template.insert(entity); + final MapInheritanceTestEntity document = template.find(entity.getId(), MapInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(Map.class))); + for (final Map.Entry entry : document.value.entrySet()) { + assertThat(entry.getValue(), is(instanceOf(ComplexBasicChildTestEntity.class))); + final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) entry.getValue(); + assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + assertThat(simpleElem.field, is(value)); + } + } + + @SuppressWarnings("rawtypes") + public static class UntypedMapInheritanceTestEntity extends BasicTestEntity { + private Map value; + } + + @SuppressWarnings("rawtypes") + @Test + public void untypedMapInheritanceMapping() { + final Map map = new HashMap<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = value; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + map.put(String.valueOf(i), child); + } + final UntypedMapInheritanceTestEntity entity = new UntypedMapInheritanceTestEntity(); + entity.value = map; + template.insert(entity); + final UntypedMapInheritanceTestEntity document = template + .find(entity.getId(), UntypedMapInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(Map.class))); + for (final Object entry : document.value.entrySet()) { + final Object val = ((Map.Entry) entry).getValue(); + assertThat(val, is(instanceOf(ComplexBasicChildTestEntity.class))); + final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) val; + assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + assertThat(simpleElem.field, is(value)); + } + } + + public static class ConstructorWithPropertyInheritanceTestEntity extends BasicTestEntity { + private final BasicTestEntity value; + + public ConstructorWithPropertyInheritanceTestEntity(final BasicTestEntity value) { + this.value = value; + } + } + + @Test + public void constructorPropertyInheritanceMapping() { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = "value"; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + final ConstructorWithPropertyInheritanceTestEntity entity = new ConstructorWithPropertyInheritanceTestEntity(child); + template.insert(entity); + final ConstructorWithPropertyInheritanceTestEntity document = template + .find(entity.getId(), ConstructorWithPropertyInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); + final ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; + assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + final SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; + assertThat(simpleDocument.field, is(innerChild.field)); + } + + public static class ListInMapInheritanceTestEntity extends BasicTestEntity { + private Map> value; + } + + @Test + public void listInMapInheritanceMapping() { + final Map> map = new HashMap<>(); + final String value = "value"; + for (int i = 0; i < 3; ++i) { + final List list = new ArrayList<>(); + map.put(String.valueOf(i), list); + for (int j = 0; j < 3; ++j) { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = value; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + list.add(child); + } + } + final ListInMapInheritanceTestEntity entity = new ListInMapInheritanceTestEntity(); + entity.value = map; + template.insert(entity); + final ListInMapInheritanceTestEntity document = template.find(entity.getId(), ListInMapInheritanceTestEntity.class) + .get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(Map.class))); + for (final Map.Entry> entry : document.value.entrySet()) { + assertThat(entry.getValue(), is(instanceOf(List.class))); + for (final BasicTestEntity elem : entry.getValue()) { + assertThat(elem, is(instanceOf(ComplexBasicChildTestEntity.class))); + final ComplexBasicChildTestEntity complexElem = (ComplexBasicChildTestEntity) elem; + assertThat(complexElem.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + final SimpleBasicChildTestEntity simpleElem = (SimpleBasicChildTestEntity) complexElem.nestedEntity; + assertThat(simpleElem.field, is(value)); + } + } + } + + public static class PropertyRefInheritanceTestEntity extends BasicTestEntity { + @Ref + private BasicTestEntity value; + } + + @Test + public void propertyRefInheritanceMapping() { + final SimpleBasicChildTestEntity innerChild = new SimpleBasicChildTestEntity(); + innerChild.field = "value"; + final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); + child.nestedEntity = innerChild; + final PropertyRefInheritanceTestEntity entity = new PropertyRefInheritanceTestEntity(); + entity.value = child; + template.insert(child); + template.insert(entity); + final PropertyRefInheritanceTestEntity document = template + .find(entity.getId(), PropertyRefInheritanceTestEntity.class).get(); + assertThat(document, is(notNullValue())); + assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); + final ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; + assertThat(complexDocument.nestedEntity, is(instanceOf(SimpleBasicChildTestEntity.class))); + final SimpleBasicChildTestEntity simpleDocument = (SimpleBasicChildTestEntity) complexDocument.nestedEntity; + assertThat(simpleDocument.field, is(innerChild.field)); + } + + public class SimpleTypesTestEntity extends BasicTestEntity { + private String stringValue; + private Boolean boolValue; + private int intValue; + private Long longValue; + private Short shortValue; + private Float floatValue; + private Double doubleValue; + private Character charValue; + private Byte byteValue; + private Date dateValue; + private java.sql.Date sqlDateValue; + private Timestamp timestampValue; + private byte[] byteArray; + } + + @Test + public void simpleTypesMapping() { + final SimpleTypesTestEntity entity = new SimpleTypesTestEntity(); + entity.stringValue = "hello world"; + entity.boolValue = true; + entity.intValue = 123456; + entity.longValue = 1234567890123456789l; + entity.shortValue = 1234; + entity.floatValue = 1.234567890f; + entity.doubleValue = 1.2345678901234567890; + entity.charValue = 'a'; + entity.byteValue = 'z'; + entity.dateValue = new Date(); + entity.sqlDateValue = new java.sql.Date(new Date().getTime()); + entity.timestampValue = new Timestamp(new Date().getTime()); + entity.byteArray = new byte[] { 'a', 'b', 'c', 'x', 'y', 'z' }; + template.insert(entity); + final SimpleTypesTestEntity document = template.find(entity.getId(), SimpleTypesTestEntity.class).get(); + assertThat(entity.stringValue, is(document.stringValue)); + assertThat(entity.boolValue, is(document.boolValue)); + assertThat(entity.intValue, is(document.intValue)); + assertThat(entity.longValue, is(document.longValue)); + assertThat(entity.shortValue, is(document.shortValue)); + assertThat(entity.floatValue, is(document.floatValue)); + assertThat(entity.doubleValue, is(document.doubleValue)); + assertThat(entity.charValue, is(document.charValue)); + assertThat(entity.byteValue, is(document.byteValue)); + assertThat(entity.dateValue, is(document.dateValue)); + assertThat(entity.sqlDateValue, is(document.sqlDateValue)); + assertThat(entity.timestampValue, is(document.timestampValue)); + assertThat(entity.byteArray, is(document.byteArray)); + } + + public enum TestEnum { + A, B; + } + + public class EnumTestEntity extends BasicTestEntity { + private TestEnum value; + } + + @Test + public void enumMapping() { + final EnumTestEntity entity = new EnumTestEntity(); + entity.value = TestEnum.A; + template.insert(entity); + final EnumTestEntity document = template.find(entity.getId(), EnumTestEntity.class).get(); + assertThat(entity.value, is(document.value)); + } +} From a138346baf958fa1b127edb0a9c1c3ef28297eb1 Mon Sep 17 00:00:00 2001 From: Christian Lechner <6638938+christian-lechner@users.noreply.github.com> Date: Mon, 11 Jun 2018 09:15:38 +0200 Subject: [PATCH 55/94] allow override of CRUD methods of ArangoRepository with @Query (#61) --- .../springframework/annotation/Query.java | 9 ++++- .../OverriddenCrudMethodsRepository.java | 37 +++++++++++++++++++ .../repository/query/ArangoAqlQueryTest.java | 37 +++++++++++++------ 3 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 src/test/java/com/arangodb/springframework/repository/OverriddenCrudMethodsRepository.java diff --git a/src/main/java/com/arangodb/springframework/annotation/Query.java b/src/main/java/com/arangodb/springframework/annotation/Query.java index de49de1de..5a3dce99a 100644 --- a/src/main/java/com/arangodb/springframework/annotation/Query.java +++ b/src/main/java/com/arangodb/springframework/annotation/Query.java @@ -25,11 +25,18 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.data.annotation.QueryAnnotation; + /** - * Created by F625633 on 12/07/2017. + * + * @author Audrius Malele + * @author Mark McCormick + * @author Mark Vollmary + * @author Christian Lechner */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) +@QueryAnnotation public @interface Query { /** diff --git a/src/test/java/com/arangodb/springframework/repository/OverriddenCrudMethodsRepository.java b/src/test/java/com/arangodb/springframework/repository/OverriddenCrudMethodsRepository.java new file mode 100644 index 000000000..42ef3eeb7 --- /dev/null +++ b/src/test/java/com/arangodb/springframework/repository/OverriddenCrudMethodsRepository.java @@ -0,0 +1,37 @@ +/* + * 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 com.arangodb.springframework.annotation.Query; +import com.arangodb.springframework.testdata.Customer; + +/** + * + * @author Christian Lechner + * + */ +public interface OverriddenCrudMethodsRepository extends ArangoRepository { + + @Override + @Query("RETURN NULL") + Iterable findAll(); + +} diff --git a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java index 2f1accc19..1c1367d6e 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/ArangoAqlQueryTest.java @@ -1,6 +1,7 @@ package com.arangodb.springframework.repository.query; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.collection.IsIn.isOneOf; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; @@ -8,12 +9,14 @@ import java.time.Instant; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -27,6 +30,7 @@ import com.arangodb.model.AqlQueryOptions; import com.arangodb.springframework.core.convert.DBDocumentEntity; import com.arangodb.springframework.repository.AbstractArangoRepositoryTest; +import com.arangodb.springframework.repository.OverriddenCrudMethodsRepository; import com.arangodb.springframework.testdata.Customer; import com.arangodb.springframework.testdata.CustomerNameProjection; @@ -40,14 +44,15 @@ @RunWith(SpringJUnit4ClassRunner.class) public class ArangoAqlQueryTest extends AbstractArangoRepositoryTest { - private final AqlQueryOptions OPTIONS = new AqlQueryOptions(); + @Autowired + protected OverriddenCrudMethodsRepository overriddenRepository; @Test public void findOneByIdAqlWithNamedParameterTest() { repository.save(customers); - final Map retrieved = repository.findOneByIdAqlWithNamedParameter(john.getId(), OPTIONS); - final Customer retrievedCustomer = template.getConverter().read(Customer.class, - new DBDocumentEntity(retrieved)); + final Map retrieved = repository.findOneByIdAqlWithNamedParameter(john.getId(), + new AqlQueryOptions()); + final Customer retrievedCustomer = template.getConverter().read(Customer.class, new DBDocumentEntity(retrieved)); assertEquals(john, retrievedCustomer); } @@ -61,7 +66,7 @@ public void findOneByIdAndNameAqlTest() { allProperties.put("_rev", retrieved.getRevision()); retrieved.getProperties().forEach((k, v) -> allProperties.put(k, v)); final Customer retrievedCustomer = template.getConverter().read(Customer.class, - new DBDocumentEntity(allProperties)); + new DBDocumentEntity(allProperties)); assertEquals(john, retrievedCustomer); } @@ -71,8 +76,8 @@ public void findOneByBindVarsAqlTest() { final Map bindVars = new HashMap<>(); bindVars.put("id", john.getId()); bindVars.put("name", john.getName()); - final ArangoCursor retrieved = repository.findOneByBindVarsAql(OPTIONS.ttl(127).cache(true), - bindVars); + final ArangoCursor retrieved = repository.findOneByBindVarsAql(new AqlQueryOptions().ttl(127).cache(true), + bindVars); assertEquals(john, retrieved.next()); } @@ -109,7 +114,7 @@ public void findOneByIdAndNameWithBindVarsAqlTest() { public void findOneByIdInCollectionAqlWithUnusedParamTest() { repository.save(customers); final Customer retrieved = repository.findOneByIdInCollectionAqlWithUnusedParam(john.getId().split("/")[0], - john.getId(), john.getId()); + john.getId(), john.getId()); assertEquals(john, retrieved); } @@ -117,7 +122,7 @@ public void findOneByIdInCollectionAqlWithUnusedParamTest() { public void findOneByIdInNamedCollectionAqlWithUnusedParamTest() { repository.save(customers); final Customer retrieved = repository.findOneByIdInNamedCollectionAqlWithUnusedParam(john.getId().split("/")[0], - john.getId(), john.getId()); + john.getId(), john.getId()); assertEquals(john, retrieved); } @@ -125,7 +130,7 @@ public void findOneByIdInNamedCollectionAqlWithUnusedParamTest() { public void findOneByIdInIncorrectNamedCollectionAqlTest() { repository.save(customers); final Customer retrieved = repository.findOneByIdInIncorrectNamedCollectionAql(john.getId().split("/")[0], - john.getId(), john.getId()); + john.getId(), john.getId()); assertEquals(john, retrieved); } @@ -133,7 +138,7 @@ public void findOneByIdInIncorrectNamedCollectionAqlTest() { public void findOneByIdInNamedCollectionAqlRejectedTest() { repository.save(customers); final Customer retrieved = repository.findOneByIdInNamedCollectionAqlRejected(john.getId().split("/")[0], - john.getId()); + john.getId()); assertEquals(john, retrieved); } @@ -185,7 +190,7 @@ public void findManyLegalAgeWithStaticProjectionTest() { public void findOneByIdWithDynamicProjectionTest() { repository.save(customers); final CustomerNameProjection retrieved = repository.findOneByIdWithDynamicProjection(john.getId(), - CustomerNameProjection.class); + CustomerNameProjection.class); assertEquals(retrieved.getName(), john.getName()); } @@ -231,4 +236,12 @@ public void sortTest() { assertThat(retrieved, is(toBeRetrieved)); } + @Test + public void overriddenCrudMethodsTest() { + overriddenRepository.save(customers); + Iterator customers = overriddenRepository.findAll().iterator(); + assertThat(customers.hasNext(), is(true)); + assertThat(customers.next(), is(nullValue())); + } + } From 256ce06e30069e1c7fb75ed1546cf86376a9f8bf Mon Sep 17 00:00:00 2001 From: haqer1 Date: Thu, 14 Jun 2018 12:35:14 +0600 Subject: [PATCH 56/94] Fix-ups for issue #62. (#63) * Fix-ups for issue #62. * An additional fix-up for issue #62. * A few more fix-ups for issue #62: in From, Ref, Relations, To. --- .../com/arangodb/springframework/annotation/Document.java | 4 ++-- .../java/com/arangodb/springframework/annotation/Edge.java | 4 ++-- .../java/com/arangodb/springframework/annotation/From.java | 2 +- .../java/com/arangodb/springframework/annotation/Ref.java | 2 +- .../com/arangodb/springframework/annotation/Relations.java | 2 +- src/main/java/com/arangodb/springframework/annotation/To.java | 2 +- .../springframework/core/convert/DefaultArangoConverter.java | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/annotation/Document.java b/src/main/java/com/arangodb/springframework/annotation/Document.java index 5f0e00859..100754155 100644 --- a/src/main/java/com/arangodb/springframework/annotation/Document.java +++ b/src/main/java/com/arangodb/springframework/annotation/Document.java @@ -56,7 +56,7 @@ * of k means that k-1 replicas are kept. Any two copies reside on different DBServers. Replication between * them is synchronous, that is, every write operation to the "leader" copy will be replicated to all * "follower" replicas, before the write operation is reported successful. If a server fails, this is - * detected automatically and one of the servers holding copies take over, usually without an error being + * detected automatically and one of the servers holding copies takes over, usually without an error being * reported. */ int replicationFactor() default -1; @@ -107,7 +107,7 @@ boolean isSystem() default false; /** - * @return The: number of buckets into which indexes using a hash table are split. The default is 16 and this number + * @return The number of buckets into which indexes using a hash table are split. The default is 16 and this number * has to be a power of 2 and less than or equal to 1024. For very large collections one should increase * this to avoid long pauses when the hash table has to be initially built or resized, since buckets are * resized individually and can be initially built in parallel. For example, 64 might be a sensible value diff --git a/src/main/java/com/arangodb/springframework/annotation/Edge.java b/src/main/java/com/arangodb/springframework/annotation/Edge.java index ac45a685b..8bc3f1dfd 100644 --- a/src/main/java/com/arangodb/springframework/annotation/Edge.java +++ b/src/main/java/com/arangodb/springframework/annotation/Edge.java @@ -56,7 +56,7 @@ * of k means that k-1 replicas are kept. Any two copies reside on different DBServers. Replication between * them is synchronous, that is, every write operation to the "leader" copy will be replicated to all * "follower" replicas, before the write operation is reported successful. If a server fails, this is - * detected automatically and one of the servers holding copies take over, usually without an error being + * detected automatically and one of the servers holding copies takes over, usually without an error being * reported. */ int replicationFactor() default -1; @@ -107,7 +107,7 @@ boolean isSystem() default false; /** - * @return The: number of buckets into which indexes using a hash table are split. The default is 16 and this number + * @return The number of buckets into which indexes using a hash table are split. The default is 16 and this number * has to be a power of 2 and less than or equal to 1024. For very large collections one should increase * this to avoid long pauses when the hash table has to be initially built or resized, since buckets are * resized individually and can be initially built in parallel. For example, 64 might be a sensible value diff --git a/src/main/java/com/arangodb/springframework/annotation/From.java b/src/main/java/com/arangodb/springframework/annotation/From.java index d78b09f23..fb7f44a3b 100644 --- a/src/main/java/com/arangodb/springframework/annotation/From.java +++ b/src/main/java/com/arangodb/springframework/annotation/From.java @@ -34,7 +34,7 @@ public @interface From { /** - * @return whether the entity should be loaded lazy + * @return whether the entity should be loaded lazily */ boolean lazy() default false; diff --git a/src/main/java/com/arangodb/springframework/annotation/Ref.java b/src/main/java/com/arangodb/springframework/annotation/Ref.java index 5c2d4ef52..bac07edd3 100644 --- a/src/main/java/com/arangodb/springframework/annotation/Ref.java +++ b/src/main/java/com/arangodb/springframework/annotation/Ref.java @@ -37,7 +37,7 @@ public @interface Ref { /** - * @return whether the entity should be loaded lazy + * @return whether the entity should be loaded lazily */ boolean lazy() default false; diff --git a/src/main/java/com/arangodb/springframework/annotation/Relations.java b/src/main/java/com/arangodb/springframework/annotation/Relations.java index 6c40e3ffe..404ba042f 100644 --- a/src/main/java/com/arangodb/springframework/annotation/Relations.java +++ b/src/main/java/com/arangodb/springframework/annotation/Relations.java @@ -58,7 +58,7 @@ public enum Direction { Direction direction() default Direction.ANY; /** - * @return whether the entity should be loaded lazy + * @return whether the entity should be loaded lazily */ boolean lazy() default false; diff --git a/src/main/java/com/arangodb/springframework/annotation/To.java b/src/main/java/com/arangodb/springframework/annotation/To.java index d5686b765..fc39b9d86 100644 --- a/src/main/java/com/arangodb/springframework/annotation/To.java +++ b/src/main/java/com/arangodb/springframework/annotation/To.java @@ -34,7 +34,7 @@ public @interface To { /** - * @return whether the entity should be loaded lazy + * @return whether the entity should be loaded lazily */ boolean lazy() default false; diff --git a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java index 355095e21..5175bbabd 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java @@ -263,7 +263,7 @@ private Optional readReference( ids = (Collection) asCollection(source); } catch (final Exception e) { throw new MappingException( - "Collection of Type String expected for references but found type " + source.getClass()); + "Collection of type String expected for references but found type " + source.getClass()); } return Optional.ofNullable(resolver.resolveMultiple(ids, property.getTypeInformation(), annotation)); } else { From 9782c8f3e0a26e2269d6a05def10de0de23bc029 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 14 Jun 2018 08:49:41 +0200 Subject: [PATCH 57/94] Remove unnecessary equality check (issue #64) --- .../springframework/repository/query/AbstractArangoQuery.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java index a6134f4da..c4f912b7f 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java +++ b/src/main/java/com/arangodb/springframework/repository/query/AbstractArangoQuery.java @@ -24,7 +24,6 @@ import java.util.HashMap; import java.util.Map; -import org.springframework.data.geo.GeoPage; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ResultProcessor; import org.springframework.util.Assert; @@ -64,7 +63,7 @@ public Object execute(final Object[] parameters) { options = new AqlQueryOptions(); } - if (method.isPageQuery() || GeoPage.class.equals(method.getReturnType())) { + if (method.isPageQuery()) { options.fullCount(true); } From c11eeeb0330a44e9101fceba2ef4603d47b93608 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 14 Jun 2018 10:33:10 +0200 Subject: [PATCH 58/94] Upgrade java-driver & prepare release --- ChangeLog | 4 +- pom.xml | 4 +- .../core/template/ArangoExtCursor.java | 103 +++++++++--------- 3 files changed, 57 insertions(+), 54 deletions(-) diff --git a/ChangeLog b/ChangeLog index db3476214..8d5fd90c8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,8 @@ -v1.1.6 (xxxx-xx-xx) +v1.1.6 (2018-06-14) --------------------------- * fixed lazy use of @Relations/@From/@To when using a Set<> +* allow override of CRUD methods of ArangoRepository with @Query +* upgraded dependency arangodb-java-driver 4.5.0 v1.1.5 (2018-06-07) --------------------------- diff --git a/pom.xml b/pom.xml index 53e3bd9d4..0816d5296 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.6-SNAPSHOT + 1.1.6 2017 jar @@ -25,7 +25,7 @@ 1.1.3 1.3 4.12 - 4.4.1 + 4.5.0 4.3.13.RELEASE ${spring.version} ${spring.version} diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java index 6f5909b7b..0c21ce9b4 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java @@ -1,51 +1,52 @@ -/* - * 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.core.template; - -import com.arangodb.ArangoCursor; -import com.arangodb.entity.CursorEntity; -import com.arangodb.internal.ArangoCursorExecute; -import com.arangodb.internal.ArangoCursorIterator; -import com.arangodb.internal.InternalArangoDatabase; -import com.arangodb.springframework.core.convert.ArangoConverter; - -/** - * @author Mark Vollmary - * @param - * - */ -class ArangoExtCursor extends ArangoCursor { - - protected ArangoExtCursor(final InternalArangoDatabase db, final ArangoCursorExecute execute, - final Class type, final CursorEntity result, final ArangoConverter converter) { - super(db, execute, type, result); - ArangoExtCursorIterator.class.cast(iterator).setConverter(converter); - } - - @Override - protected ArangoCursorIterator createIterator( - final ArangoCursor cursor, - final InternalArangoDatabase db, - final ArangoCursorExecute execute, - final CursorEntity result) { - return new ArangoExtCursorIterator<>(cursor, db, execute, result); - } -} +/* + * 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.core.template; + +import com.arangodb.ArangoCursor; +import com.arangodb.entity.CursorEntity; +import com.arangodb.internal.ArangoCursorExecute; +import com.arangodb.internal.ArangoCursorImpl; +import com.arangodb.internal.ArangoCursorIterator; +import com.arangodb.internal.InternalArangoDatabase; +import com.arangodb.springframework.core.convert.ArangoConverter; + +/** + * @author Mark Vollmary + * @param + * + */ +class ArangoExtCursor extends ArangoCursorImpl { + + protected ArangoExtCursor(final InternalArangoDatabase db, final ArangoCursorExecute execute, + final Class type, final CursorEntity result, final ArangoConverter converter) { + super(db, execute, type, result); + ArangoExtCursorIterator.class.cast(iterator).setConverter(converter); + } + + @Override + protected ArangoCursorIterator createIterator( + final ArangoCursor cursor, + final InternalArangoDatabase db, + final ArangoCursorExecute execute, + final CursorEntity result) { + return new ArangoExtCursorIterator<>(cursor, db, execute, result); + } +} From a72e75c7bc38fcfb45a3431d65d89177ab2b7862 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 15 Jun 2018 08:40:15 +0200 Subject: [PATCH 59/94] prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0816d5296..ba7f288f2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.6 + 1.1.7-SNAPSHOT 2017 jar From 3c8615b4a08fb3ea8d980391b6915f048269ea6d Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 21 Jun 2018 08:45:10 +0200 Subject: [PATCH 60/94] Disable test (cause https://github.com/arangodb/arangodb/issues/5303) --- .../repository/query/derived/DerivedQueryCreatorTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java b/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java index 478eab829..c4311c01c 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java @@ -11,6 +11,7 @@ import java.util.Locale; import java.util.Set; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.data.domain.Page; @@ -620,6 +621,7 @@ public void referenceTest() { } @Test + @Ignore // https://github.com/arangodb/arangodb/issues/5303 public void referenceGeospatialTest() { final List toBeRetrieved = new LinkedList<>(); final Customer customer1 = new Customer("", "", 0); From 6b4ba50ef77ce3743b4ec51e01217be65ef8cee2 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 25 Jun 2018 14:43:56 +0200 Subject: [PATCH 61/94] prepare release 1.1.7 --- ChangeLog | 6 ++++++ pom.xml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 8d5fd90c8..efe132b1e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +v1.1.7 (2018-06-25) +--------------------------- +* upgraded dependency arangodb-java-driver 4.5.2 + * fixed ArangoDB.aquireHostList(true) with authentication + * added support for custom serializer + v1.1.6 (2018-06-14) --------------------------- * fixed lazy use of @Relations/@From/@To when using a Set<> diff --git a/pom.xml b/pom.xml index ba7f288f2..6237f49ec 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.7-SNAPSHOT + 1.1.7 2017 jar From 44093eadcdabc1320633a2fe0df5ad1053571cae Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 25 Jun 2018 14:54:20 +0200 Subject: [PATCH 62/94] prepare release --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6237f49ec..90fd7584d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.7 + 1.1.8-SNAPSHOT 2017 jar From 3fb01b7a5f13ff9510a9847fc6540456c0648b25 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 26 Jun 2018 08:24:02 +0200 Subject: [PATCH 63/94] Change ChangeLog format --- ChangeLog | 53 -------- ChangeLog.md | 99 ++++++++++++++ README.md | 359 +++++++++++++++++++++++++++------------------------ 3 files changed, 287 insertions(+), 224 deletions(-) delete mode 100644 ChangeLog create mode 100644 ChangeLog.md diff --git a/ChangeLog b/ChangeLog deleted file mode 100644 index efe132b1e..000000000 --- a/ChangeLog +++ /dev/null @@ -1,53 +0,0 @@ -v1.1.7 (2018-06-25) ---------------------------- -* upgraded dependency arangodb-java-driver 4.5.2 - * fixed ArangoDB.aquireHostList(true) with authentication - * added support for custom serializer - -v1.1.6 (2018-06-14) ---------------------------- -* fixed lazy use of @Relations/@From/@To when using a Set<> -* allow override of CRUD methods of ArangoRepository with @Query -* upgraded dependency arangodb-java-driver 4.5.0 - -v1.1.5 (2018-06-07) ---------------------------- -* fixed relation cycle (issue #43) - -v1.1.4 (2018-06-07) ---------------------------- -* upgraded arangodb-java-driver to 4.4.1 -* fixed relation cycle (issue #43) - -v1.1.3 (2018-06-04) ---------------------------- -* fixed support for ArangoCusor as query-method return type -* added paging and sorting support for native queries - -v1.1.2 (2018-05-04) ---------------------------- -* deprecated @Param annotation, there is already such an annotation from Spring Data -* fixed floating point numbers in derived queries -* added support for named queries -* fixed distance calculation in derived geo queries - -v1.1.1 (2018-04-23) ---------------------------- -* fixed serialization of enums (issue #39) - -v1.1.0 (2018-04-20) ---------------------------- -* added DataIntegrityViolationException to ExceptionTranslator -* fixed race conditions in ArangoTemplate when creating database and collections (issue #35) -* added type mapper implementation & custom conversions extension (issue #33) -* fixed missing deserialization of return types of @Query methods(issue #21) -* fixed handling of java.time in converters (issue #36, #24, #25) -* fixed handling of org.joda.time in converters (issue #36) - -v2.0.3 (2018-03-23) ---------------------------- -* fixed missing WITH information in derived query - -v1.0.1 (2018-01-26) ---------------------------- -* fixed missing WITH information in AQL when resolving annotation @Relations (Issue #8) diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 000000000..3d586c474 --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,99 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.1.7] - 2018-06-25 + +### Changed + +- upgraded dependency arangodb-java-driver 4.5.2 + - fixed `ArangoDB#aquireHostList(true)` with authentication + - added support for custom serializer + +## [1.1.6] - 2018-06-14 + +### Changed + +- allow override of CRUD methods of `ArangoRepository` with `@Query` +- upgraded dependency arangodb-java-driver 4.5.0 + +### Fixed + +- fixed lazy use of `@Relations`/`@From`/`@To` when using a `Set<>` + +## [1.1.5] - 2018-06-07 + +### Fixed + +- fixed relation cycle (issue #43) + +## [1.1.4] - 2018-06-07 + +### Changed + +- upgraded arangodb-java-driver to 4.4.1 + +### Fixed + +- fixed relation cycle (issue #43) + +## [1.1.3] - 2018-06-04 + +### Added + +- added paging and sorting support for native queries + +### Fixed + +- fixed support for `ArangoCusor` as query-method return type + +## [1.1.2] - 2018-05-04 + +### Added + +- added support for named queries + +### Deprecated + +- deprecated `@Param` annotation, there is already such an annotation from Spring Data + +### Fixed + +- fixed floating point numbers in derived queries +- fixed distance calculation in derived geo queries + +## [1.1.1] - 2018-04-23 + +### Fixed + +- fixed serialization of enums (issue #39) + +## [1.1.0] - 2018-04-20 + +### Added + +- added `DataIntegrityViolationException` to `ExceptionTranslator` +- added type mapper implementation & custom conversions extension (issue #33) + +### Fixed + +- fixed race conditions in `ArangoTemplate` when creating database and collections (issue #35) +- fixed missing deserialization of return types of `@Query` methods(issue #21) +- fixed handling of `java.time` in converters (issue #36, #24, #25) +- fixed handling of `org.joda.time` in converters (issue #36) + +## [2.0.3] - 2018-03-23 + +### Fixed + +- fixed missing WITH information in derived query + +## [1.0.1] - 2018-01-26 + +### Fixed + +- fixed missing WITH information in AQL when resolving annotation `@Relations` (Issue #8) diff --git a/README.md b/README.md index c8227303d..05a4c02c7 100644 --- a/README.md +++ b/README.md @@ -16,46 +16,48 @@ Spring Data ArangoDB requires ArangoDB 3.0 or higher - which you can download [h **Note**: ArangoDB 3.0 does not support the default transport protocol [VelocyStream](https://github.com/arangodb/velocystream). A manual switch to HTTP is required. See chapter [configuration](#configuration). Also ArangoDB 3.0 does not support [geospatial queries](#geospatial-queries). ## Learn more -* [ArangoDB](https://www.arangodb.com/) -* [Demo](https://github.com/arangodb/spring-data-demo) -* [JavaDoc](http://arangodb.github.io/spring-data/javadoc-1_0/index.html) -* [JavaDoc Java driver](http://arangodb.github.io/arangodb-java-driver/javadoc-4_2/index.html) + +- [ArangoDB](https://www.arangodb.com/) +- [Demo](https://github.com/arangodb/spring-data-demo) +- [JavaDoc](http://arangodb.github.io/spring-data/javadoc-1_0/index.html) +- [JavaDoc Java driver](http://arangodb.github.io/arangodb-java-driver/javadoc-4_2/index.html) +- [Changelog](ChangeLog.md) ## Table of Contents -* [Getting Started](#getting-started) - * [Configuration](#configuration) -* [Template](#template) -* [Repositories](#repositories) - * [Introduction](#introduction) - * [Instantiating](#instantiating) - * [Return types](#return-types) - * [Query methods](#query-methods) - * [Derived queries](#derived-queries) - * [Geospatial queries](#geospatial-queries) - * [Property expression](#property-expression) - * [Special parameter handling](#special-parameter-handling) - * [Bind parameters](#bind-parameters) - * [AQL query options](#aql-query-options) -* [Mapping](#mapping) - * [Introduction](#introduction) - * [Conventions](#conventions) - * [Type conventions](#type-conventions) - * [Annotations](#annotations) - * [Annotation overview](#annotation-overview) - * [Document](#document) - * [Edge](#edge) - * [Reference](#reference) - * [Relations](#relations) - * [Document with From and To](#document-with-from-and-to) - * [Edge with From and To](#edge-with-from-and-to) - * [Index and Indexed annotations](#index-and-indexed-annotations) +- [Getting Started](#getting-started) + - [Configuration](#configuration) +- [Template](#template) +- [Repositories](#repositories) + - [Introduction](#introduction) + - [Instantiating](#instantiating) + - [Return types](#return-types) + - [Query methods](#query-methods) + - [Derived queries](#derived-queries) + - [Geospatial queries](#geospatial-queries) + - [Property expression](#property-expression) + - [Special parameter handling](#special-parameter-handling) + - [Bind parameters](#bind-parameters) + - [AQL query options](#aql-query-options) +- [Mapping](#mapping) + - [Introduction](#introduction) + - [Conventions](#conventions) + - [Type conventions](#type-conventions) + - [Annotations](#annotations) + - [Annotation overview](#annotation-overview) + - [Document](#document) + - [Edge](#edge) + - [Reference](#reference) + - [Relations](#relations) + - [Document with From and To](#document-with-from-and-to) + - [Edge with From and To](#edge-with-from-and-to) + - [Index and Indexed annotations](#index-and-indexed-annotations) # Getting Started To use Spring Data ArangoDB in your project, your build automation tool needs to be configured to include and use the Spring Data ArangoDB dependency. Example with Maven: -``` xml +```xml com.arangodb arangodb-spring-data @@ -69,7 +71,7 @@ There is a [demonstration app](https://github.com/arangodb/spring-data-demo), wh You can use Java to configure your Spring Data environment as show below. Setting up the underlying driver (`ArangoDB.Builder`) with default configuration automatically loads a properties file `arangodb.properties`, if it exists in the classpath. -``` java +```java @Configuration @EnableArangoRepositories(basePackages = { "com.company.mypackage" }) public class MyConfiguration extends AbstractArangoConfiguration { @@ -90,18 +92,18 @@ public class MyConfiguration extends AbstractArangoConfiguration { The driver is configured with some default values: -property-key | description | default value --------------|-------------|-------------- -arangodb.host | ArangoDB host | 127.0.0.1 -arangodb.port | ArangoDB port | 8529 -arangodb.timeout | socket connect timeout(millisecond) | 0 -arangodb.user | Basic Authentication User | -arangodb.password | Basic Authentication Password | -arangodb.useSsl | use SSL connection | false +| property-key | description | default value | +| ----------------- | ----------------------------------- | ------------- | +| arangodb.host | ArangoDB host | 127.0.0.1 | +| arangodb.port | ArangoDB port | 8529 | +| arangodb.timeout | socket connect timeout(millisecond) | 0 | +| arangodb.user | Basic Authentication User | +| arangodb.password | Basic Authentication Password | +| arangodb.useSsl | use SSL connection | false | To customize the configuration, the parameters can be changed in the Java code. -``` java +```java @Override public ArangoDB.Builder arango() { ArangoDB.Builder arango = new ArangoDB.Builder() @@ -112,9 +114,10 @@ public ArangoDB.Builder arango() { } ``` -In addition you can use the *arangodb.properties* or a custom properties file to supply credentials to the driver. +In addition you can use the _arangodb.properties_ or a custom properties file to supply credentials to the driver. + +_Properties file_ -*Properties file* ``` arangodb.host=127.0.0.1 arangodb.port=8529 @@ -123,8 +126,9 @@ arangodb.user=root arangodb.password= ``` -*Custom properties file* -``` java +_Custom properties file_ + +```java @Override public ArangoDB.Builder arango() { InputStream in = MyClass.class.getResourceAsStream("my.properties"); @@ -136,7 +140,7 @@ public ArangoDB.Builder arango() { **Note**: When using ArangoDB 3.0 it is required to set the transport protocol to HTTP and fetch the dependency `org.apache.httpcomponents:httpclient`. -``` java +```java @Override public ArangoDB.Builder arango() { ArangoDB.Builder arango = new ArangoDB.Builder() @@ -144,7 +148,8 @@ public ArangoDB.Builder arango() { return arango; } ``` -``` xml + +```xml org.apache.httpcomponents httpclient @@ -167,7 +172,7 @@ Spring Data Commons provides a composable repository infrastructure which Spring Instances of a Repository are created in Spring beans through the auto-wired mechanism of Spring. -``` java +```java public class MySpringBean { @Autowired @@ -189,7 +194,7 @@ There are three ways of passing bind parameters to the query in the query annota Using number matching, arguments will be substituted into the query in the order they are passed to the query method. -``` java +```java public interface MyRepository extends Repository{ @Query("FOR c IN customers FILTER c.name == @0 AND c.surname == @2 RETURN c") @@ -200,7 +205,7 @@ public interface MyRepository extends Repository{ With the `@Param` annotation, the argument will be placed in the query at the place corresponding to the value passed to the `@Param` annotation. -``` java +```java public interface MyRepository extends Repository{ @Query("FOR c IN customers FILTER c.name == @name AND c.surname == @surname RETURN c") @@ -209,9 +214,9 @@ public interface MyRepository extends Repository{ } ``` - In addition you can use a parameter of type `Map` annotated with `@BindVars` as your bind parameters. You can then fill the map with any parameter used in the query. (see [here](https://docs.arangodb.com/3.1/AQL/Fundamentals/BindParameters.html#bind-parameters) for more Information about Bind Parameters). +In addition you can use a parameter of type `Map` annotated with `@BindVars` as your bind parameters. You can then fill the map with any parameter used in the query. (see [here](https://docs.arangodb.com/3.1/AQL/Fundamentals/BindParameters.html#bind-parameters) for more Information about Bind Parameters). -``` java +```java public interface MyRepository extends Repository{ @Query("FOR c IN customers FILTER c.name == @name AND c.surname = @surname RETURN c") @@ -222,7 +227,7 @@ public interface MyRepository extends Repository{ A mixture of any of these methods can be used. Parameters with the same name from an `@Param` annotation will override those in the `bindVars`. -``` java +```java public interface MyRepository extends Repository{ @Query("FOR c IN customers FILTER c.name == @name AND c.surname = @surname RETURN c") @@ -237,33 +242,32 @@ Spring Data ArangoDB supports queries derived from methods names by splitting it The complete list of part types for derived methods is below, where doc is a document in the database -Keyword | Sample | Predicate -----------|----------------|-------- -IsGreaterThan, GreaterThan, After | findByAgeGreaterThan(int age) | doc.age > age -IsGreaterThanEqual, GreaterThanEqual | findByAgeIsGreaterThanEqual(int age) | doc.age >= age -IsLessThan, LessThan, Before | findByAgeIsLessThan(int age) | doc.age < age -IsLessThanEqualLessThanEqual | findByAgeLessThanEqual(int age) | doc.age <= age -IsBetween, Between | findByAgeBetween(int lower, int upper) | lower < doc.age < upper -IsNotNull, NotNull | findByNameNotNull() | doc.name != null -IsNull, Null | findByNameNull() | doc.name == null -IsLike, Like | findByNameLike(String name) | doc.name LIKE name -IsNotLike, NotLike | findByNameNotLike(String name) | NOT(doc.name LIKE name) -IsStartingWith, StartingWith, StartsWith | findByNameStartsWith(String prefix) | doc.name LIKE prefix -IsEndingWith, EndingWith, EndsWith | findByNameEndingWith(String suffix) | doc.name LIKE suffix -Regex, MatchesRegex, Matches | findByNameRegex(String pattern) | REGEX_TEST(doc.name, name, ignoreCase) -(No Keyword) | findByFirstName(String name) | doc.name == name -IsTrue, True | findByActiveTrue() | doc.active == true -IsFalse, False | findByActiveFalse() | doc.active == false -Is, Equals | findByAgeEquals(int age) | doc.age == age -IsNot, Not | findByAgeNot(int age) | doc.age != age -IsIn, In | findByNameIn(String[] names) | doc.name IN names -IsNotIn, NotIn | findByNameIsNotIn(String[] names) | doc.name NOT IN names -IsContaining, Containing, Contains | findByFriendsContaining(String name) | name IN doc.friends -IsNotContaining, NotContaining, NotContains | findByFriendsNotContains(String name) | name NOT IN doc.friends -Exists | findByFriendNameExists() | HAS(doc.friend, name) - - -``` java +| Keyword | Sample | Predicate | +| ------------------------------------------- | -------------------------------------- | -------------------------------------- | +| IsGreaterThan, GreaterThan, After | findByAgeGreaterThan(int age) | doc.age > age | +| IsGreaterThanEqual, GreaterThanEqual | findByAgeIsGreaterThanEqual(int age) | doc.age >= age | +| IsLessThan, LessThan, Before | findByAgeIsLessThan(int age) | doc.age < age | +| IsLessThanEqualLessThanEqual | findByAgeLessThanEqual(int age) | doc.age <= age | +| IsBetween, Between | findByAgeBetween(int lower, int upper) | lower < doc.age < upper | +| IsNotNull, NotNull | findByNameNotNull() | doc.name != null | +| IsNull, Null | findByNameNull() | doc.name == null | +| IsLike, Like | findByNameLike(String name) | doc.name LIKE name | +| IsNotLike, NotLike | findByNameNotLike(String name) | NOT(doc.name LIKE name) | +| IsStartingWith, StartingWith, StartsWith | findByNameStartsWith(String prefix) | doc.name LIKE prefix | +| IsEndingWith, EndingWith, EndsWith | findByNameEndingWith(String suffix) | doc.name LIKE suffix | +| Regex, MatchesRegex, Matches | findByNameRegex(String pattern) | REGEX_TEST(doc.name, name, ignoreCase) | +| (No Keyword) | findByFirstName(String name) | doc.name == name | +| IsTrue, True | findByActiveTrue() | doc.active == true | +| IsFalse, False | findByActiveFalse() | doc.active == false | +| Is, Equals | findByAgeEquals(int age) | doc.age == age | +| IsNot, Not | findByAgeNot(int age) | doc.age != age | +| IsIn, In | findByNameIn(String[] names) | doc.name IN names | +| IsNotIn, NotIn | findByNameIsNotIn(String[] names) | doc.name NOT IN names | +| IsContaining, Containing, Contains | findByFriendsContaining(String name) | name IN doc.friends | +| IsNotContaining, NotContaining, NotContains | findByFriendsNotContains(String name) | name NOT IN doc.friends | +| Exists | findByFriendNameExists() | HAS(doc.friend, name) | + +```java public interface MyRepository extends Repository { // FOR c IN customers FILTER c.name == @0 RETURN c @@ -284,7 +288,7 @@ public interface MyRepository extends Repository { You can apply sorting for one or multiple sort criteria by appending `OrderBy` to the method and `Asc` or `Desc` for the directions. -``` java +```java public interface MyRepository extends Repository { // FOR c IN customers @@ -306,9 +310,9 @@ Geospatial queries are a subsection of derived queries. To use a geospatial quer As a subsection of derived queries, geospatial queries support all the same return types, but also support the three return types `GeoPage, GeoResult and Georesults`. These types must be used in order to get the distance of each document as generated by the query. -There are two kinds of geospatial query, Near and Within. Near sorts documents by distance from the given point, while within both sorts and filters documents, returning those within the given distance range or shape. +There are two kinds of geospatial query, Near and Within. Near sorts documents by distance from the given point, while within both sorts and filters documents, returning those within the given distance range or shape. -``` java +```java public interface MyRepository extends Repository { GeoResult getByLocationNear(Point point); @@ -327,7 +331,7 @@ public interface MyRepository extends Repository { Property expressions can refer only to direct and nested properties of the managed domain class. The algorithm checks the domain class for the entire expression as the property. If the check fails, the algorithm splits up the expression at the camel case parts from the right and tries to find the corresponding property. -``` java +```java @Document("customers") public class Customer { private Address address; @@ -346,9 +350,9 @@ public interface MyRepository extends Repository { } ``` -It is possible for the algorithm to select the wrong property if the domain class also has a property which matches the first split of the expression. To resolve this ambiguity you can use _ as a separator inside your method-name to define traversal points. +It is possible for the algorithm to select the wrong property if the domain class also has a property which matches the first split of the expression. To resolve this ambiguity you can use \_ as a separator inside your method-name to define traversal points. -``` java +```java @Document("customers") public class Customer { private Address address; @@ -385,7 +389,7 @@ public interface MyRepository extends Repository { AQL supports the usage of [bind parameters](https://docs.arangodb.com/3.1/AQL/Fundamentals/BindParameters.html) which you can define with a method parameter named `bindVars` of type `Map`. -``` java +```java public interface MyRepository extends Repository { @Query("FOR c IN customers FILTER c[@field] == @value RETURN c") @@ -407,7 +411,7 @@ You can set additional options for the query and the created cursor over the cla The `AqlQueryOptions` allows you to set the cursor time-to-life, batch-size, caching flag and several other settings. This special parameter works with both query-methods and finder-methods. Keep in mind that some options, like time-to-life, are only effective if the method return type is`ArangoCursor` or `Iterable`. -``` java +```java public interface MyRepository extends Repository { @@ -437,73 +441,73 @@ In this section we will describe the features and conventions for mapping Java o ## Conventions -* The Java class name is mapped to the collection name -* The non-static fields of a Java object are used as fields in the stored document -* The Java field name is mapped to the stored document field name -* All nested Java object are stored as nested objects in the stored document -* The Java class needs a constructor which meets the following criteria: - * in case of a single constructor: - * a non-parameterized constructor or - * a parameterized constructor - * in case of multiple constructors: - * a non-parameterized constructor or - * a parameterized constructor annotated with `@PersistenceConstructor` +- The Java class name is mapped to the collection name +- The non-static fields of a Java object are used as fields in the stored document +- The Java field name is mapped to the stored document field name +- All nested Java object are stored as nested objects in the stored document +- The Java class needs a constructor which meets the following criteria: + - in case of a single constructor: + - a non-parameterized constructor or + - a parameterized constructor + - in case of multiple constructors: + - a non-parameterized constructor or + - a parameterized constructor annotated with `@PersistenceConstructor` ## Type conventions ArangoDB uses [VelocyPack](https://github.com/arangodb/velocypack) as it's internal storage format which supports a large number of data types. In addition Spring Data ArangoDB offers - with the underlying Java driver - built-in converters to add additional types to the mapping. -Java type | VelocyPack type -----------|---------------- -java.lang.String | string -java.lang.Boolean | bool -java.lang.Integer | signed int 4 bytes, smallint -java.lang.Long | signed int 8 bytes, smallint -java.lang.Short | signed int 2 bytes, smallint -java.lang.Double | double -java.lang.Float | double -java.math.BigInteger | signed int 8 bytes, unsigned int 8 bytes -java.math.BigDecimal | double -java.lang.Number | double -java.lang.Character | string -java.util.Date | string (date-format ISO 8601) -java.sql.Date | string (date-format ISO 8601) -java.sql.Timestamp | string (date-format ISO 8601) -java.util.UUID | string -java.lang.byte[] | string (Base64) +| Java type | VelocyPack type | +| -------------------- | ---------------------------------------- | +| java.lang.String | string | +| java.lang.Boolean | bool | +| java.lang.Integer | signed int 4 bytes, smallint | +| java.lang.Long | signed int 8 bytes, smallint | +| java.lang.Short | signed int 2 bytes, smallint | +| java.lang.Double | double | +| java.lang.Float | double | +| java.math.BigInteger | signed int 8 bytes, unsigned int 8 bytes | +| java.math.BigDecimal | double | +| java.lang.Number | double | +| java.lang.Character | string | +| java.util.Date | string (date-format ISO 8601) | +| java.sql.Date | string (date-format ISO 8601) | +| java.sql.Timestamp | string (date-format ISO 8601) | +| java.util.UUID | string | +| java.lang.byte[] | string (Base64) | ## Annotations ### Annotation overview -annotation | level | description ------------|-------|------------ -@Document | class | marks this class as a candidate for mapping -@Edge | class | marks this class as a candidate for mapping -@Id | field | stores the field as the system field _id -@Key | field | stores the field as the system field _key -@Rev | field | stores the field as the system field _rev -@Field("alt-name") | field | stores the field with an alternative name -@Ref | field | stores the _id of the referenced document and not the nested document -@From | field | stores the _id of the referenced document as the system field _from -@To | field | stores the _id of the referenced document as the system field _to -@Relations | field | vertices which are connected over edges -@HashIndex | class | describes a hash index -@HashIndexed | field | describes how to index the field -@SkiplistIndex | class | describes a skiplist index -@SkiplistIndexed | field | describes how to index the field -@PersistentIndex | class | describes a persistent index -@PersistentIndexed | field | describes how to index the field -@GeoIndex | class | describes a geo index -@GeoIndexed | field | describes how to index the field -@FulltextIndex | class | describes a fulltext index -@FulltextIndexed | field | describes how to index the field +| annotation | level | description | +| ------------------ | ----- | ---------------------------------------------------------------------- | +| @Document | class | marks this class as a candidate for mapping | +| @Edge | class | marks this class as a candidate for mapping | +| @Id | field | stores the field as the system field \_id | +| @Key | field | stores the field as the system field \_key | +| @Rev | field | stores the field as the system field \_rev | +| @Field("alt-name") | field | stores the field with an alternative name | +| @Ref | field | stores the \_id of the referenced document and not the nested document | +| @From | field | stores the \_id of the referenced document as the system field \_from | +| @To | field | stores the \_id of the referenced document as the system field \_to | +| @Relations | field | vertices which are connected over edges | +| @HashIndex | class | describes a hash index | +| @HashIndexed | field | describes how to index the field | +| @SkiplistIndex | class | describes a skiplist index | +| @SkiplistIndexed | field | describes how to index the field | +| @PersistentIndex | class | describes a persistent index | +| @PersistentIndexed | field | describes how to index the field | +| @GeoIndex | class | describes a geo index | +| @GeoIndexed | field | describes how to index the field | +| @FulltextIndex | class | describes a fulltext index | +| @FulltextIndexed | field | describes how to index the field | ### Document The annotations `@Document` applied to a class marks this class as a candidate for mapping to the database. The most relevant parameter is `value` to specify the collection name in the database. The annotation `@Document` specifies the collection type to `DOCUMENT`. -``` java +```java @Document(value="persons") public class Person { ... @@ -514,7 +518,7 @@ public class Person { The annotations `@Edge` applied to a class marks this class as a candidate for mapping to the database. The most relevant parameter is `value` to specify the collection name in the database. The annotation `@Edge` specifies the collection type to `EDGE`. -``` java +```java @Edge("relations") public class Relation { ... @@ -525,7 +529,7 @@ public class Relation { With the annotation `@Ref` applied on a field the nested object isn’t stored as a nested object in the document. The `_id` field of the nested object is stored in the document and the nested object has to be stored as a separate document in another collection described in the `@Document` annotation of the nested object class. To successfully persist an instance of your object the referencing field has to be null or it's instance has to provide a field with the annotation `@Id` including a valid id. -``` java +```java @Document(value="persons") public class Person { @Ref @@ -541,7 +545,7 @@ public class Address { } ``` -The database representation of `Person` in collection *persons* looks as follow: +The database representation of `Person` in collection _persons_ looks as follow: ``` { @@ -550,7 +554,9 @@ The database representation of `Person` in collection *persons* looks as follow: "address" : "addresses/456" } ``` -and the representation of `Address` in collection *addresses*: + +and the representation of `Address` in collection _addresses_: + ``` { "_key" : "456", @@ -577,7 +583,7 @@ Without the annotation `@Ref` at the field `address`, the stored document would With the annotation `@Relations` applied on a collection or array field in a class annotated with `@Document` the nested objects are fetched from the database over a graph traversal with your current object as the starting point. The most relevant parameter is `edge`. With `edge` you define the edge collection - which should be used in the traversal - using the class type. With the parameter `depth` you can define the maximal depth for the traversal (default 1) and the parameter `direction` defines whether the traversal should follow outgoing or incoming edges (default Direction.ANY). -``` java +```java @Document(value="persons") public class Person { @Relations(edge=Relation.class, depth=1, direction=Direction.ANY) @@ -592,9 +598,9 @@ public class Relation { ### Document with From and To -With the annotations `@From` and `@To` applied on a collection or array field in a class annotated with `@Document` the nested edge objects are fetched from the database. Each of the nested edge objects has to be stored as separate edge document in the edge collection described in the `@Edge` annotation of the nested object class with the *_id* of the parent document as field *_from* or *_to*. +With the annotations `@From` and `@To` applied on a collection or array field in a class annotated with `@Document` the nested edge objects are fetched from the database. Each of the nested edge objects has to be stored as separate edge document in the edge collection described in the `@Edge` annotation of the nested object class with the _\_id_ of the parent document as field _\_from_ or _\_to_. -``` java +```java @Document("persons") public class Person { @From @@ -607,7 +613,8 @@ public class Relation { } ``` -The database representation of `Person` in collection *persons* looks as follow: +The database representation of `Person` in collection _persons_ looks as follow: + ``` { "_key" : "123", @@ -615,7 +622,8 @@ The database representation of `Person` in collection *persons* looks as follow: } ``` -and the representation of `Relation` in collection *relations*: +and the representation of `Relation` in collection _relations_: + ``` { "_key" : "456", @@ -630,14 +638,13 @@ and the representation of `Relation` in collection *relations*: "_to" : ".../..." } ... - ``` ### Edge with From and To -With the annotations `@From` and `@To` applied on a field in a class annotated with `@Edge` the nested object is fetched from the database. The nested object has to be stored as a separate document in the collection described in the `@Document` annotation of the nested object class. The *_id* field of this nested object is stored in the fields `_from` or `_to` within the edge document. +With the annotations `@From` and `@To` applied on a field in a class annotated with `@Edge` the nested object is fetched from the database. The nested object has to be stored as a separate document in the collection described in the `@Document` annotation of the nested object class. The _\_id_ field of this nested object is stored in the fields `_from` or `_to` within the edge document. -``` java +```java @Edge("relations") public class Relation { @From @@ -653,7 +660,8 @@ public class Person { } ``` -The database representation of `Relation` in collection *relations* looks as follow: +The database representation of `Relation` in collection _relations_ looks as follow: + ``` { "_key" : "123", @@ -663,7 +671,8 @@ The database representation of `Relation` in collection *relations* looks as fol } ``` -and the representation of `Person` in collection *persons*: +and the representation of `Person` in collection _persons_: + ``` { "_key" : "456", @@ -682,14 +691,16 @@ and the representation of `Person` in collection *persons*: With the `@Indexed` annotations user defined indexes can be created at a collection level by annotating single fields of a class. Possible `@Indexed` annotations are: -* `@HashIndexed` -* `@SkiplistIndexed` -* `@PersistentIndexed` -* `@GeoIndexed` -* `@FulltextIndexed` + +- `@HashIndexed` +- `@SkiplistIndexed` +- `@PersistentIndexed` +- `@GeoIndexed` +- `@FulltextIndexed` The following example creates a hash index on the field `name` and a separate hash index on the field `age`: -``` java + +```java public class Person { @HashIndexed private String name; @@ -702,7 +713,8 @@ public class Person { With the `@Indexed` annotations different indexes can be created on the same field. The following example creates a hash index and also a skiplist index on the field `name`: -``` java + +```java public class Person { @HashIndexed @SkiplistIndexed @@ -713,14 +725,16 @@ public class Person { If the index should include multiple fields the `@Index` annotations can be used on the type instead. Possible `@Index` annotations are: -* `@HashIndex` -* `@SkiplistIndex` -* `@PersistentIndex` -* `@GeoIndex` -* `@FulltextIndex` + +- `@HashIndex` +- `@SkiplistIndex` +- `@PersistentIndex` +- `@GeoIndex` +- `@FulltextIndex` The following example creates a single hash index on the fields `name` and `age`, note that if a field is renamed in the database with @Field, the new field name must be used in the index declaration: -``` java + +```java @HashIndex(fields = {"fullname", "age"}) public class Person { @Field("fullname") @@ -733,7 +747,8 @@ public class Person { The `@Index` annotations can also be used to create an index on a nested field. The following example creates a single hash index on the fields `name` and `address.country`: -``` java + +```java @HashIndex(fields = {"name", "address.country"}) public class Person { private String name; @@ -745,7 +760,8 @@ public class Person { The `@Index` annotations and the `@Indexed` annotations can be used at the same time in one class. The following example creates a hash index on the fields `name` and `age` and a separate hash index on the field `age`: -``` java + +```java @HashIndex(fields = {"name", "age"}) public class Person { private String name; @@ -758,7 +774,8 @@ public class Person { The `@Index` annotations can be used multiple times to create more than one index in this way. The following example creates a hash index on the fields `name` and `age` and a separate hash index on the fields `name` and `gender`: -``` java + +```java @HashIndex(fields = {"name", "age"}) @HashIndex(fields = {"name", "gender"}) public class Person { From 88418a1b1473fe33805c8a45e52a6028fcc8953d Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 26 Jun 2018 11:00:38 +0200 Subject: [PATCH 64/94] Fix derived query with `containing` on `String` (issue #84) --- ChangeLog.md | 4 + .../query/derived/DerivedQueryCreator.java | 128 +++++++++--------- .../repository/CustomerRepository.java | 4 + .../derived/DerivedQueryCreatorTest.java | 35 +++++ 4 files changed, 107 insertions(+), 64 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 3d586c474..1dac761a2 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Fixed + +- fixed derived query with `containing` on `String` (issue #84) + ## [1.1.7] - 2018-06-25 ### Changed diff --git a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java index d7d667015..b5a56c505 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java +++ b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java @@ -91,9 +91,9 @@ public class DerivedQueryCreator extends AbstractQueryCreator, ArangoPersistentProperty> context, - final Class domainClass, final PartTree tree, final ArangoParameterAccessor accessor, - final Map bindVars, final List geoFields, final boolean useFunctions) { + final MappingContext, ArangoPersistentProperty> context, + final Class domainClass, final PartTree tree, final ArangoParameterAccessor accessor, + final Map bindVars, final List geoFields, final boolean useFunctions) { super(tree, accessor); this.context = context; this.collectionName = collectionName(context.getPersistentEntity(domainClass).getCollection()); @@ -144,8 +144,8 @@ protected ConjunctionBuilder or(final ConjunctionBuilder base, final Conjunction } /** - * Builds a full AQL query from a built Disjunction, additional information from PartTree and special parameters - * caught by ArangoParameterAccessor + * Builds a full AQL query from a built Disjunction, additional information from + * PartTree and special parameters caught by ArangoParameterAccessor * * @param criteria * @param sort @@ -161,19 +161,19 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { } final Disjunction disjunction = disjunctionBuilder.build(); final String array = disjunction.getArray().length() == 0 ? collectionName : disjunction.getArray(); - final String predicate = disjunction.getPredicate().length() == 0 ? "" - : " FILTER " + disjunction.getPredicate(); + final String predicate = disjunction.getPredicate().length() == 0 ? "" : " FILTER " + disjunction.getPredicate(); final String queryTemplate = "%sFOR e IN %s%s%s%s%s%s%s"; // collection predicate count sort limit pageable // queryType final String count = (tree.isCountProjection() || tree.isExistsProjection()) - ? ((tree.isDistinct() ? " COLLECT entity = e" : "") + " COLLECT WITH COUNT INTO length") : ""; + ? ((tree.isDistinct() ? " COLLECT entity = e" : "") + " COLLECT WITH COUNT INTO length") + : ""; final String limit = tree.isLimiting() ? format(" LIMIT %d", tree.getMaxResults()) : ""; final String pageable = accessor.getPageable() == null ? "" : format(" LIMIT %d, %d", accessor.getPageable().getOffset(), accessor.getPageable().getPageSize()); final String geoFields = format("%s[0], %s[1]", uniqueLocation, uniqueLocation); final String distanceAdjusted = getGeoFields().isEmpty() ? "e" : format("MERGE(e, { '_distance': distance(%s, %f, %f) })", geoFields, getUniquePoint()[0], - getUniquePoint()[1]); + getUniquePoint()[1]); final String type = tree.isDelete() ? (" REMOVE e IN " + collectionName) : ((tree.isCountProjection() || tree.isExistsProjection()) ? " RETURN length" : format(" RETURN %s", distanceAdjusted)); @@ -181,7 +181,7 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { if ((!this.geoFields.isEmpty() || isUnique != null && isUnique) && !tree.isDelete() && !tree.isCountProjection() && !tree.isExistsProjection()) { final String distanceSortKey = format(" SORT distance(%s, %f, %f)", geoFields, getUniquePoint()[0], - getUniquePoint()[1]); + getUniquePoint()[1]); if (sort == null) { sortString = distanceSortKey; } else { @@ -218,7 +218,8 @@ private String ignorePropertyCase(final Part part) { } /** - * Wrapps property expression in order to lower case. Only properties of type String or Iterable are lowered + * Wrapps property expression in order to lower case. Only properties of type + * String or Iterable are lowered * * @param part * @param property @@ -241,14 +242,15 @@ private String ignorePropertyCase(final Part part, final String property) { * @return */ private String getProperty(final Part part) { - return "e." + context.getPersistentPropertyPath(part.getProperty()).toPath(null, - ArangoPersistentProperty::getFieldName); + return "e." + + context.getPersistentPropertyPath(part.getProperty()).toPath(null, ArangoPersistentProperty::getFieldName); } /** - * Creates a predicate template with one String placeholder for a Part-specific predicate expression from properties - * in PropertyPath which represent references or collections, and, also, returns a 2nd String representing property - * to be used in a Part-specific predicate expression + * Creates a predicate template with one String placeholder for a Part-specific + * predicate expression from properties in PropertyPath which represent + * references or collections, and, also, returns a 2nd String representing + * property to be used in a Part-specific predicate expression * * @param part * @return @@ -303,8 +305,7 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { simpleProperties = new StringBuilder(); final String iteration = format(TEMPLATE, entity, collection, entity, prevEntity, name); final String predicate = format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate - : format(predicateTemplate, predicate); + predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); } else { // collection final String TEMPLATE = "FOR %s IN TO_ARRAY(%s%s)"; @@ -314,8 +315,7 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { simpleProperties = new StringBuilder(); final String iteration = format(TEMPLATE, entity, prevEntity, name); final String predicate = format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate - : format(predicateTemplate, predicate); + predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); } } else { if (property.getRef().isPresent() || property.getFrom().isPresent() || property.getTo().isPresent()) { @@ -331,8 +331,7 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { simpleProperties = new StringBuilder(); final String iteration = format(TEMPLATE, entity, collection, entity, prevEntity, name); final String predicate = format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate - : format(predicateTemplate, predicate); + predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); } else { // simple property simpleProperties.append("." + property.getFieldName()); @@ -344,7 +343,8 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { } /** - * Lowers case of a given argument if its type is String, Iterable or String[] if shouldIgnoreCase is true + * Lowers case of a given argument if its type is String, Iterable or + * String[] if shouldIgnoreCase is true * * @param argument * @param shouldIgnoreCase @@ -374,8 +374,8 @@ private Object ignoreArgumentCase(final Object argument, final boolean shouldIgn } /** - * Determines whether the case for a Part should be ignored based on property type and IgnoreCase keywords in the - * method name + * Determines whether the case for a Part should be ignored based on property + * type and IgnoreCase keywords in the method name * * @param part * @return @@ -393,7 +393,8 @@ private boolean shouldIgnoreCase(final Part part) { } /** - * Puts actual arguments in bindVars Map based on Part-specific information and types of arguments. + * Puts actual arguments in bindVars Map based on Part-specific information and + * types of arguments. * * @param iterator * @param shouldIgnoreCase @@ -402,12 +403,8 @@ private boolean shouldIgnoreCase(final Part part) { * @param ignoreBindVars * @return */ - private ArgumentProcessingResult bindArguments( - final Iterator iterator, - final boolean shouldIgnoreCase, - final int arguments, - final Boolean borderStatus, - final boolean ignoreBindVars) { + private ArgumentProcessingResult bindArguments(final Iterator iterator, final boolean shouldIgnoreCase, + final int arguments, final Boolean borderStatus, final boolean ignoreBindVars) { int bindings = 0; ArgumentProcessingResult.Type type = ArgumentProcessingResult.Type.DEFAULT; for (int i = 0; i < arguments; ++i) { @@ -453,8 +450,7 @@ private ArgumentProcessingResult bindArguments( checkUniquePoint(circle.getCenter()); bindVars.put(Integer.toString(bindingCounter + bindings++), circle.getCenter().getY()); bindVars.put(Integer.toString(bindingCounter + bindings++), circle.getCenter().getX()); - bindVars.put(Integer.toString(bindingCounter + bindings++), - convertDistanceToMeters(circle.getRadius())); + bindVars.put(Integer.toString(bindingCounter + bindings++), convertDistanceToMeters(circle.getRadius())); break; } else if (caseAdjusted.getClass() == Point.class) { final Point point = (Point) caseAdjusted; @@ -485,8 +481,8 @@ private ArgumentProcessingResult bindArguments( } /** - * Ensures that Points used in geospatial parts of non-nested properties are the same in case geospatial return type - * is expected + * Ensures that Points used in geospatial parts of non-nested properties are the + * same in case geospatial return type is expected * * @param point */ @@ -500,7 +496,7 @@ private void checkUniquePoint(final Point point) { } if (!geoFields.isEmpty()) { Assert.isTrue(uniquePoint == null || uniquePoint.equals(point), - "Different Points are used - Distance is ambiguous"); + "Different Points are used - Distance is ambiguous"); uniquePoint = point; } } @@ -522,8 +518,8 @@ private double convertDistanceToMeters(final Distance distance) { } /** - * Ensures that the same geo fields are used in geospatial parts of non-nested properties are the same in case - * geospatial return type is expected + * Ensures that the same geo fields are used in geospatial parts of non-nested + * properties are the same in case geospatial return type is expected * * @param part */ @@ -540,8 +536,8 @@ private void checkUniqueLocation(final Part part) { } /** - * Creates a PartInformation containing a String representing either a predicate or array expression, and binds - * arguments from Iterator for a given Part + * Creates a PartInformation containing a String representing either a predicate + * or array expression, and binds arguments from Iterator for a given Part * * @param part * @param iterator @@ -595,7 +591,7 @@ private PartInformation createPartInformation(final Part part, final Iterator context.getPersistentEntity(t) != null).map(t -> t.getType()) - .ifPresent(t -> with.add(t)); + .filter(t -> context.getPersistentEntity(t) != null).map(t -> t.getType()).ifPresent(t -> with.add(t)); } while ((pp = pp.next()) != null); return clause == null ? null : new PartInformation(isArray, clause, with); } @@ -777,7 +776,8 @@ private String format(final String format, final Object... args) { } /** - * Stores how many bindings where used in a Part and if or what kind of special type clause should be created + * Stores how many bindings where used in a Part and if or what kind of special + * type clause should be created */ private static class ArgumentProcessingResult { diff --git a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java index 1617a3f22..ef516663d 100644 --- a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java +++ b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java @@ -98,6 +98,10 @@ Collection findTop2DistinctByStringArrayContainingIgnoreCaseOrIntegerL Collection findByStringArrayNotContainingIgnoreCase(String string); + Collection findByNameContaining(String string); + + Collection findByNameContainingIgnoreCase(String string); + int countByAgeGreaterThanOrStringArrayNullAndIntegerList(int age, List integerList); Integer countDistinctByAliveTrueOrNameLikeOrAgeLessThanEqual(String pattern, int age); diff --git a/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java b/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java index c4311c01c..eb74da997 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java @@ -162,6 +162,41 @@ public void NotContainingTest() { assertTrue(equals(retrieved, toBeRetrieved, cmp, eq, false)); } + @Test + public void findByStringContaining() { + final Customer c1 = new Customer("abc", "", 0); + final Customer c2 = new Customer("Abc", "", 0); + final Customer c3 = new Customer("abcd", "", 0); + final Customer c4 = new Customer("ab", "", 0); + repository.save(c1); + repository.save(c2); + repository.save(c3); + repository.save(c4); + final Collection toBeRetrieved = new LinkedList<>(); + toBeRetrieved.add(c1); + toBeRetrieved.add(c3); + final Collection retrieved = repository.findByNameContaining("abc"); + assertTrue(equals(retrieved, toBeRetrieved, cmp, eq, false)); + } + + @Test + public void findByStringContainingIgnoreCase() { + final Customer c1 = new Customer("abc", "", 0); + final Customer c2 = new Customer("Abc", "", 0); + final Customer c3 = new Customer("abcd", "", 0); + final Customer c4 = new Customer("ab", "", 0); + repository.save(c1); + repository.save(c2); + repository.save(c3); + repository.save(c4); + final Collection toBeRetrieved = new LinkedList<>(); + toBeRetrieved.add(c1); + toBeRetrieved.add(c2); + toBeRetrieved.add(c3); + final Collection retrieved = repository.findByNameContainingIgnoreCase("abc"); + assertTrue(equals(retrieved, toBeRetrieved, cmp, eq, false)); + } + @Test public void findTest() { final Customer customer1 = new Customer("%_\\name", "%surname%", 20); From ad8de3f98c9e3e83b80ce9f09bd85f4464618af3 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 26 Jun 2018 11:30:47 +0200 Subject: [PATCH 65/94] Prepare release 1.1.8 --- ChangeLog.md | 2 ++ pom.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 1dac761a2..e2e1f886a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [1.1.8] - 2018-06-26 + ### Fixed - fixed derived query with `containing` on `String` (issue #84) diff --git a/pom.xml b/pom.xml index 90fd7584d..2a3275b89 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.8-SNAPSHOT + 1.1.8 2017 jar From bf365872ab1efa062471581d8f4680e24210e780 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 26 Jun 2018 11:33:57 +0200 Subject: [PATCH 66/94] Prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2a3275b89..5fd55ce94 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.8 + 1.1.9-SNAPSHOT 2017 jar From b35e95535baa6579961f1149a1bfa9bdadb81a0f Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 26 Jun 2018 15:00:54 +0200 Subject: [PATCH 67/94] Add support for satellite collections --- ChangeLog.md | 6 ++++++ .../com/arangodb/springframework/annotation/Document.java | 6 ++++++ .../java/com/arangodb/springframework/annotation/Edge.java | 6 ++++++ .../core/mapping/DefaultArangoPersistentEntity.java | 3 +++ 4 files changed, 21 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index e2e1f886a..8aef58f3d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Added + +- added support for satellite collections + - added `@Document#satellite()` + - added `@Edge#satellite()` + ## [1.1.8] - 2018-06-26 ### Fixed diff --git a/src/main/java/com/arangodb/springframework/annotation/Document.java b/src/main/java/com/arangodb/springframework/annotation/Document.java index 100754155..6439524b7 100644 --- a/src/main/java/com/arangodb/springframework/annotation/Document.java +++ b/src/main/java/com/arangodb/springframework/annotation/Document.java @@ -61,6 +61,12 @@ */ int replicationFactor() default -1; + /** + * @return If true the collection is created as a satellite collection. In this case {@link #replicationFactor()} is + * ignored. + */ + boolean satellite() default false; + /** * @return If true then the data is synchronized to disk before returning from a document create, update, replace or * removal operation. (default: false) diff --git a/src/main/java/com/arangodb/springframework/annotation/Edge.java b/src/main/java/com/arangodb/springframework/annotation/Edge.java index 8bc3f1dfd..a87fb92be 100644 --- a/src/main/java/com/arangodb/springframework/annotation/Edge.java +++ b/src/main/java/com/arangodb/springframework/annotation/Edge.java @@ -61,6 +61,12 @@ */ int replicationFactor() default -1; + /** + * @return If true the collection is created as a satellite collection. In this case {@link #replicationFactor()} is + * ignored. + */ + boolean satellite() default false; + /** * @return If true then the data is synchronized to disk before returning from a document create, update, replace or * removal operation. (default: false) 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 26b88e8ae..f45884441 100644 --- a/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentEntity.java +++ b/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentEntity.java @@ -125,6 +125,9 @@ private static CollectionCreateOptions createCollectionOptions(final Document an if (annotation.replicationFactor() > -1) { options.replicationFactor(annotation.replicationFactor()); } + if (annotation.satellite()) { + options.satellite(annotation.satellite()); + } final String[] shardKeys = annotation.shardKeys(); if (shardKeys.length > 1 || (shardKeys.length > 0 && StringUtils.hasText(shardKeys[0]))) { options.shardKeys(shardKeys); From 35676ef3c0b7043bc76e318f8c8313fad2139bcb Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 26 Jun 2018 15:02:43 +0200 Subject: [PATCH 68/94] Add support for streaming AQL cursors --- ChangeLog.md | 1 + .../springframework/annotation/QueryOptions.java | 14 ++++++++++++++ .../repository/query/ArangoQueryMethod.java | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 8aef58f3d..6bbd69e9f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added +- added support for streaming AQL cursors - added support for satellite collections - added `@Document#satellite()` - added `@Edge#satellite()` diff --git a/src/main/java/com/arangodb/springframework/annotation/QueryOptions.java b/src/main/java/com/arangodb/springframework/annotation/QueryOptions.java index 6c2fdd237..21e754501 100644 --- a/src/main/java/com/arangodb/springframework/annotation/QueryOptions.java +++ b/src/main/java/com/arangodb/springframework/annotation/QueryOptions.java @@ -87,4 +87,18 @@ */ int ttl() default -1; + /** + * @since ArangoDB 3.4.0 + * @return Specify true and the query will be executed in a streaming fashion. The query result is not stored on the + * server, but calculated on the fly. Beware: long-running queries will need to hold the collection locks + * for as long as the query cursor exists. When set to false a query will be executed right away in its + * entirety. In that case query results are either returned right away (if the resultset is small enough), + * or stored on the arangod instance and accessible via the cursor API (with respect to the ttl). It is + * advisable to only use this option on short-running queries or without exclusive locks (write-locks on + * MMFiles). Please note that the query options cache, count and fullCount will not work on streaming + * queries. Additionally query statistics, warnings and profiling data will only be available after the + * query is finished. The default value is false + */ + boolean stream() default false; + } diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java index 992c4219f..3945be3dd 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java @@ -115,6 +115,10 @@ public AqlQueryOptions getAnnotatedQueryOptions() { options.fullCount(queryOptions.fullCount()); options.profile(queryOptions.profile()); options.rules(Arrays.asList(queryOptions.rules())); + final boolean stream = queryOptions.stream(); + if (stream) { + options.stream(stream); + } return options; } From ee6fd530361e259ea691b0178e5684e7cc22217a Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 26 Jun 2018 15:04:08 +0200 Subject: [PATCH 69/94] Add QueryOptions#memoryLimit() --- ChangeLog.md | 2 ++ .../arangodb/springframework/annotation/QueryOptions.java | 8 ++++++++ .../repository/query/ArangoQueryMethod.java | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 6bbd69e9f..ecdae1d20 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added - added support for streaming AQL cursors + - added `QueryOptions#stream()` +- added `QueryOptions#memoryLimit()` - added support for satellite collections - added `@Document#satellite()` - added `@Edge#satellite()` diff --git a/src/main/java/com/arangodb/springframework/annotation/QueryOptions.java b/src/main/java/com/arangodb/springframework/annotation/QueryOptions.java index 21e754501..3fd744ef3 100644 --- a/src/main/java/com/arangodb/springframework/annotation/QueryOptions.java +++ b/src/main/java/com/arangodb/springframework/annotation/QueryOptions.java @@ -101,4 +101,12 @@ */ boolean stream() default false; + /** + * @since ArangoDB 3.1.0 + * @return the maximum number of memory (measured in bytes) that the query is allowed to use. If set, then the query + * will fail with error "resource limit exceeded" in case it allocates too much memory. A value of 0 + * indicates that there is no memory limit. + */ + long memoryLimit() default -1; + } diff --git a/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java b/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java index 3945be3dd..342af90e8 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java +++ b/src/main/java/com/arangodb/springframework/repository/query/ArangoQueryMethod.java @@ -119,6 +119,10 @@ public AqlQueryOptions getAnnotatedQueryOptions() { if (stream) { options.stream(stream); } + final long memoryLimit = queryOptions.memoryLimit(); + if (memoryLimit != -1) { + options.memoryLimit(memoryLimit); + } return options; } From 1ac9c283694c2fc199b3f93f3a84bc2ad3924b2e Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 28 Jun 2018 17:00:58 +0200 Subject: [PATCH 70/94] Upgrade java-driver to 4.5.3-SNAPSHOT --- pom.xml | 2 +- .../core/template/ArangoCursorInitializer.java | 2 +- .../springframework/core/template/ArangoExtCursor.java | 4 ++-- .../core/template/ArangoExtCursorIterator.java | 9 +++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 5fd55ce94..593b4c142 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 1.1.3 1.3 4.12 - 4.5.0 + 4.5.3-SNAPSHOT 4.3.13.RELEASE ${spring.version} ${spring.version} diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoCursorInitializer.java b/src/main/java/com/arangodb/springframework/core/template/ArangoCursorInitializer.java index 9c21afe07..7e2a4af38 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoCursorInitializer.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoCursorInitializer.java @@ -41,7 +41,7 @@ public ArangoCursorInitializer(final ArangoConverter converter) { @Override public ArangoCursor createInstance( - final InternalArangoDatabase db, + final InternalArangoDatabase db, final ArangoCursorExecute execute, final Class type, final CursorEntity result) { diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java index 0c21ce9b4..fc70591d9 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursor.java @@ -35,7 +35,7 @@ */ class ArangoExtCursor extends ArangoCursorImpl { - protected ArangoExtCursor(final InternalArangoDatabase db, final ArangoCursorExecute execute, + protected ArangoExtCursor(final InternalArangoDatabase db, final ArangoCursorExecute execute, final Class type, final CursorEntity result, final ArangoConverter converter) { super(db, execute, type, result); ArangoExtCursorIterator.class.cast(iterator).setConverter(converter); @@ -44,7 +44,7 @@ protected ArangoExtCursor(final InternalArangoDatabase db, final Ara @Override protected ArangoCursorIterator createIterator( final ArangoCursor cursor, - final InternalArangoDatabase db, + final InternalArangoDatabase db, final ArangoCursorExecute execute, final CursorEntity result) { return new ArangoExtCursorIterator<>(cursor, db, execute, result); diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java index 5de2ee93b..40ecca760 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java @@ -42,7 +42,7 @@ /** * @author Mark Vollmary * @author Christian Lechner - * + * * @param * */ @@ -53,7 +53,8 @@ class ArangoExtCursorIterator extends ArangoCursorIterator { static { final Set> simpleTypes = new HashSet<>(); - // the following types apply only if the VPackJdk8Module is present on the ArangoDB Java driver, + // the following types apply only if the VPackJdk8Module is present on the + // ArangoDB Java driver, // but there is no possibility to check simpleTypes.add(Instant.class); simpleTypes.add(LocalDate.class); @@ -66,8 +67,8 @@ class ArangoExtCursorIterator extends ArangoCursorIterator { private ArangoConverter converter; - protected ArangoExtCursorIterator(final ArangoCursor cursor, final InternalArangoDatabase db, - final ArangoCursorExecute execute, final CursorEntity result) { + protected ArangoExtCursorIterator(final ArangoCursor cursor, final InternalArangoDatabase db, + final ArangoCursorExecute execute, final CursorEntity result) { super(cursor, execute, db, result); } From 40ea4d87fad15d99862c7846339d1f6ba411771f Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 2 Jul 2018 13:19:54 +0200 Subject: [PATCH 71/94] Upgrade dependency arangodb-java-driver 4.6.0 --- ChangeLog.md | 4 ++++ pom.xml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index ecdae1d20..e003da925 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -15,6 +15,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - added `@Document#satellite()` - added `@Edge#satellite()` +## Changed + +- upgraded dependency arangodb-java-driver 4.6.0 + ## [1.1.8] - 2018-06-26 ### Fixed diff --git a/pom.xml b/pom.xml index 593b4c142..36fb8a4d9 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ 1.1.3 1.3 4.12 - 4.5.3-SNAPSHOT + 4.6.0 4.3.13.RELEASE ${spring.version} ${spring.version} From 4b841cd6e014ee7cd3fe50d66cdb231920a5c0c3 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 2 Jul 2018 14:36:14 +0200 Subject: [PATCH 72/94] Add ArangoOperations#repsert() --- .../core/ArangoOperations.java | 36 ++++- .../core/template/ArangoTemplate.java | 135 ++++++++---------- .../repository/SimpleArangoRepository.java | 18 ++- 3 files changed, 106 insertions(+), 83 deletions(-) diff --git a/src/main/java/com/arangodb/springframework/core/ArangoOperations.java b/src/main/java/com/arangodb/springframework/core/ArangoOperations.java index 1ad867b60..b762a142a 100644 --- a/src/main/java/com/arangodb/springframework/core/ArangoOperations.java +++ b/src/main/java/com/arangodb/springframework/core/ArangoOperations.java @@ -377,7 +377,7 @@ MultiDocumentEntity insert(Iterable values, Cla * @param value * A representation of a single document * @param options - * Additional options, can be null + * Additional options, can be null * @return information about the document * @throws DataAccessException */ @@ -385,14 +385,14 @@ DocumentEntity insert(String collectionName, Object value, DocumentCreateOptions throws DataAccessException; /** - + * * Creates a new document from the given document, unless there is already a document with the _key given. If no * _key is given, a new unique _key is generated automatically. * * @param collectionName - * Name of the collection in which the new document should be inserted + * Name of the collection in which the new document should be inserted * @param value - * A representation of a single document + * A representation of a single document * @return information about the document * @throws DataAccessException */ @@ -406,26 +406,54 @@ public enum UpsertStrategy { * Creates a new document from the given document, unless there is already a document with the id given. In that * case it updates or replaces the document, depending on the chosen strategy. * + * @deprecated use {@link #repsert(Object)} instead * @param value * A representation of a single document * @param strategy * The strategy to use when not inserting the document * @throws DataAccessException */ + @Deprecated void upsert(T value, UpsertStrategy strategy) throws DataAccessException; /** * Creates new documents from the given documents, unless there already exists. In that case it updates or replaces * the documents, depending on the chosen strategy. * + * @deprecated use {@link #repsert(Iterable)} instead * @param value * A List of documents * @param strategy * The strategy to use when not inserting the document * @throws DataAccessException */ + @Deprecated void upsert(Iterable value, UpsertStrategy strategy) throws DataAccessException; + /** + * Creates a new document from the given document, unless there is already a document with the id given. In that + * case it replaces the document. + * + * @param value + * A representation of a single document + * @throws DataAccessException + * @since ArangoDB 3.4 + */ + void repsert(T value) throws DataAccessException; + + /** + * Creates new documents from the given documents, unless there already exists. In that case it replaces the + * documents. + * + * @param value + * A List of documents + * @param entityClass + * The entity type of the documents + * @throws DataAccessException + * @since ArangoDB 3.4 + */ + void repsert(Iterable value, Class entityClass) throws DataAccessException; + /** * Checks whether the document exists by reading a single document head * 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 fb6015698..0da56a173 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -98,10 +98,10 @@ public ArangoTemplate(final ArangoDB.Builder arango, final String database, fina } public ArangoTemplate(final ArangoDB.Builder arango, final String database, final ArangoConverter converter, - final PersistenceExceptionTranslator exceptionTranslator) { + final PersistenceExceptionTranslator exceptionTranslator) { super(); - this.arango = arango.build()._setCursorInitializer( - new com.arangodb.springframework.core.template.ArangoCursorInitializer(converter)); + this.arango = arango.build() + ._setCursorInitializer(new com.arangodb.springframework.core.template.ArangoCursorInitializer(converter)); this.databaseName = database; this.converter = converter; this.exceptionTranslator = exceptionTranslator; @@ -111,7 +111,8 @@ public ArangoTemplate(final ArangoDB.Builder arango, final String database, fina } private ArangoDatabase db() { - // guard against NPE because database can be set to null by dropDatabase() by another thread + // guard against NPE because database can be set to null by dropDatabase() by + // another thread ArangoDatabase db = database; if (db != null) { return db; @@ -154,16 +155,13 @@ private ArangoCollection _collection(final Class entityClass) { } private ArangoCollection _collection(final Class entityClass, final String id) { - final ArangoPersistentEntity persistentEntity = converter.getMappingContext() - .getPersistentEntity(entityClass); + final ArangoPersistentEntity persistentEntity = converter.getMappingContext().getPersistentEntity(entityClass); final String name = determineCollectionFromId(Optional.ofNullable(id)).orElse(persistentEntity.getCollection()); return _collection(name, persistentEntity, persistentEntity.getCollectionOptions()); } - private ArangoCollection _collection( - final String name, - final ArangoPersistentEntity persistentEntity, - final CollectionCreateOptions options) { + private ArangoCollection _collection(final String name, final ArangoPersistentEntity persistentEntity, + final CollectionCreateOptions options) { return collectionCache.computeIfAbsent(name, collName -> { final ArangoCollection collection = db().collection(collName); @@ -187,9 +185,8 @@ private ArangoCollection _collection( }); } - private static void ensureCollectionIndexes( - final CollectionOperations collection, - final ArangoPersistentEntity persistentEntity) { + private static void ensureCollectionIndexes(final CollectionOperations collection, + final ArangoPersistentEntity persistentEntity) { persistentEntity.getHashIndexes().stream().forEach(index -> ensureHashIndex(collection, index)); persistentEntity.getHashIndexedProperties().stream().forEach(p -> ensureHashIndex(collection, p)); persistentEntity.getSkiplistIndexes().stream().forEach(index -> ensureSkiplistIndex(collection, index)); @@ -203,14 +200,13 @@ private static void ensureCollectionIndexes( } private static void ensureHashIndex(final CollectionOperations collection, final HashIndex annotation) { - collection.ensureHashIndex(Arrays.asList(annotation.fields()), new HashIndexOptions() - .unique(annotation.unique()).sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); + collection.ensureHashIndex(Arrays.asList(annotation.fields()), new HashIndexOptions().unique(annotation.unique()) + .sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); } private static void ensureHashIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { final HashIndexOptions options = new HashIndexOptions(); - value.getHashIndexed() - .ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); + value.getHashIndexed().ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); collection.ensureHashIndex(Collections.singleton(value.getFieldName()), options); } @@ -219,9 +215,7 @@ private static void ensureSkiplistIndex(final CollectionOperations collection, f .unique(annotation.unique()).sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); } - private static void ensureSkiplistIndex( - final CollectionOperations collection, - final ArangoPersistentProperty value) { + private static void ensureSkiplistIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { final SkiplistIndexOptions options = new SkiplistIndexOptions(); value.getSkiplistIndexed() .ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); @@ -230,20 +224,18 @@ private static void ensureSkiplistIndex( private static void ensurePersistentIndex(final CollectionOperations collection, final PersistentIndex annotation) { collection.ensurePersistentIndex(Arrays.asList(annotation.fields()), - new PersistentIndexOptions().unique(annotation.unique()).sparse(annotation.sparse())); + new PersistentIndexOptions().unique(annotation.unique()).sparse(annotation.sparse())); } - private static void ensurePersistentIndex( - final CollectionOperations collection, - final ArangoPersistentProperty value) { + private static void ensurePersistentIndex(final CollectionOperations collection, + final ArangoPersistentProperty value) { final PersistentIndexOptions options = new PersistentIndexOptions(); value.getPersistentIndexed().ifPresent(i -> options.unique(i.unique()).sparse(i.sparse())); collection.ensurePersistentIndex(Collections.singleton(value.getFieldName()), options); } private static void ensureGeoIndex(final CollectionOperations collection, final GeoIndex annotation) { - collection.ensureGeoIndex(Arrays.asList(annotation.fields()), - new GeoIndexOptions().geoJson(annotation.geoJson())); + collection.ensureGeoIndex(Arrays.asList(annotation.fields()), new GeoIndexOptions().geoJson(annotation.geoJson())); } private static void ensureGeoIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { @@ -254,12 +246,10 @@ private static void ensureGeoIndex(final CollectionOperations collection, final private static void ensureFulltextIndex(final CollectionOperations collection, final FulltextIndex annotation) { collection.ensureFulltextIndex(Collections.singleton(annotation.field()), - new FulltextIndexOptions().minLength(annotation.minLength() > -1 ? annotation.minLength() : null)); + new FulltextIndexOptions().minLength(annotation.minLength() > -1 ? annotation.minLength() : null)); } - private static void ensureFulltextIndex( - final CollectionOperations collection, - final ArangoPersistentProperty value) { + private static void ensureFulltextIndex(final CollectionOperations collection, 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); @@ -312,14 +302,11 @@ public ArangoCursor query(final String query, final AqlQueryOptions optio } @Override - public ArangoCursor query( - final String query, - final Map bindVars, - final AqlQueryOptions options, - final Class entityClass) throws DataAccessException { + public ArangoCursor query(final String query, final Map bindVars, + final AqlQueryOptions options, final Class entityClass) throws DataAccessException { return db().query(query, - bindVars == null ? null : DBDocumentEntity.class.cast(toDBEntity(prepareBindVars(bindVars))), options, - entityClass); + bindVars == null ? null : DBDocumentEntity.class.cast(toDBEntity(prepareBindVars(bindVars))), options, + entityClass); } private Map prepareBindVars(final Map bindVars) { @@ -332,22 +319,19 @@ private Map prepareBindVars(final Map bindVars) } @Override - public MultiDocumentEntity delete( - final Iterable values, - final Class entityClass, - final DocumentDeleteOptions options) throws DataAccessException { + public MultiDocumentEntity delete(final Iterable values, final Class entityClass, + final DocumentDeleteOptions options) throws DataAccessException { try { - return _collection(entityClass).deleteDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), - entityClass, options); + return _collection(entityClass).deleteDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), entityClass, + options); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } } @Override - public MultiDocumentEntity delete( - final Iterable values, - final Class entityClass) throws DataAccessException { + public MultiDocumentEntity delete(final Iterable values, final Class entityClass) + throws DataAccessException { return delete(values, entityClass, new DocumentDeleteOptions()); } @@ -367,10 +351,8 @@ public DocumentEntity delete(final String id, final Class entityClass) throws } @Override - public MultiDocumentEntity update( - final Iterable values, - final Class entityClass, - final DocumentUpdateOptions options) throws DataAccessException { + public MultiDocumentEntity update(final Iterable values, final Class entityClass, + final DocumentUpdateOptions options) throws DataAccessException { try { final MultiDocumentEntity res = _collection(entityClass) .updateDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); @@ -382,9 +364,8 @@ public MultiDocumentEntity update( } @Override - public MultiDocumentEntity update( - final Iterable values, - final Class entityClass) throws DataAccessException { + public MultiDocumentEntity update(final Iterable values, final Class entityClass) + throws DataAccessException { return update(values, entityClass, new DocumentUpdateOptions()); } @@ -393,7 +374,7 @@ public DocumentEntity update(final String id, final Object value, final Document throws DataAccessException { try { final DocumentEntity res = _collection(value.getClass(), id).updateDocument(determineDocumentKeyFromId(id), - toDBEntity(value), options); + toDBEntity(value), options); updateDBFields(value, res); return res; } catch (final ArangoDBException e) { @@ -407,10 +388,8 @@ public DocumentEntity update(final String id, final Object value) throws DataAcc } @Override - public MultiDocumentEntity replace( - final Iterable values, - final Class entityClass, - final DocumentReplaceOptions options) throws DataAccessException { + public MultiDocumentEntity replace(final Iterable values, final Class entityClass, + final DocumentReplaceOptions options) throws DataAccessException { try { final MultiDocumentEntity res = _collection(entityClass) .replaceDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); @@ -422,9 +401,8 @@ public MultiDocumentEntity replace( } @Override - public MultiDocumentEntity replace( - final Iterable values, - final Class entityClass) throws DataAccessException { + public MultiDocumentEntity replace(final Iterable values, final Class entityClass) + throws DataAccessException { return replace(values, entityClass, new DocumentReplaceOptions()); } @@ -433,7 +411,7 @@ public DocumentEntity replace(final String id, final Object value, final Documen throws DataAccessException { try { final DocumentEntity res = _collection(value.getClass(), id).replaceDocument(determineDocumentKeyFromId(id), - toDBEntity(value), options); + toDBEntity(value), options); updateDBFields(value, res); return res; } catch (final ArangoDBException e) { @@ -450,8 +428,8 @@ public DocumentEntity replace(final String id, final Object value) throws DataAc public Optional find(final String id, final Class entityClass, final DocumentReadOptions options) throws DataAccessException { try { - final DBEntity doc = _collection(entityClass, id).getDocument(determineDocumentKeyFromId(id), - DBEntity.class, options); + final DBEntity doc = _collection(entityClass, id).getDocument(determineDocumentKeyFromId(id), DBEntity.class, + options); return Optional.ofNullable(fromDBEntity(entityClass, doc)); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); @@ -487,10 +465,8 @@ public Iterable find(final Iterable ids, final Class entityCla } @Override - public MultiDocumentEntity insert( - final Iterable values, - final Class entityClass, - final DocumentCreateOptions options) throws DataAccessException { + public MultiDocumentEntity insert(final Iterable values, final Class entityClass, + final DocumentCreateOptions options) throws DataAccessException { try { final MultiDocumentEntity res = _collection(entityClass) .insertDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); @@ -502,9 +478,8 @@ public MultiDocumentEntity insert( } @Override - public MultiDocumentEntity insert( - final Iterable values, - final Class entityClass) throws DataAccessException { + public MultiDocumentEntity insert(final Iterable values, final Class entityClass) + throws DataAccessException { return insert(values, entityClass, new DocumentCreateOptions()); } @@ -547,8 +522,8 @@ public void upsert(final T value, final UpsertStrategy strategy) throws Data final ArangoPersistentEntity entity = getConverter().getMappingContext().getPersistentEntity(entityClass); final ArangoPersistentProperty idProperty = entity.getIdProperty(); if (idProperty != null) { - final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor( - entity.getPropertyAccessor(value), converter.getConversionService()); + final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(value), + converter.getConversionService()); final Object id = accessor.getProperty(idProperty); if (id != null) { switch (strategy) { @@ -581,8 +556,8 @@ public void upsert(final Iterable value, final UpsertStrategy strategy) t idProperty = entity.getIdProperty(); } if (idProperty != null) { - final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor( - entity.getPropertyAccessor(e), converter.getConversionService()); + final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(e), + converter.getConversionService()); final Object id = accessor.getProperty(idProperty); if (id != null) { withId.add(e); @@ -626,6 +601,16 @@ private void updateDBFields(final Iterable values, final MultiDocumentEnt } } + @Override + public void repsert(final T value) throws DataAccessException { + insert(value, new DocumentCreateOptions().overwrite(true)); + } + + @Override + public void repsert(final Iterable value, final Class entityClass) throws DataAccessException { + insert(value, entityClass, new DocumentCreateOptions().overwrite(true)); + } + private void updateDBFields(final Object value, final DocumentEntity documentEntity) { final ArangoPersistentEntity entity = converter.getMappingContext().getPersistentEntity(value.getClass()); final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(value), diff --git a/src/main/java/com/arangodb/springframework/repository/SimpleArangoRepository.java b/src/main/java/com/arangodb/springframework/repository/SimpleArangoRepository.java index 651c3eccd..5708fa11b 100644 --- a/src/main/java/com/arangodb/springframework/repository/SimpleArangoRepository.java +++ b/src/main/java/com/arangodb/springframework/repository/SimpleArangoRepository.java @@ -24,6 +24,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.stream.StreamSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,10 +77,14 @@ public SimpleArangoRepository(final ArangoOperations arangoOperations, final Cla * @param entity the entity to be saved to the database * @return the updated entity with any id/key/rev saved */ - // TODO refactor once template.upsert() is implemented + @SuppressWarnings("deprecation") @Override public S save(final S entity) { - arangoOperations.upsert(entity, UpsertStrategy.UPDATE); + if (arangoOperations.getVersion().getVersion().compareTo("3.4.0") < 0) { + arangoOperations.upsert(entity, UpsertStrategy.UPDATE); + } else { + arangoOperations.repsert(entity); + } return entity; } @@ -90,10 +95,15 @@ public S save(final S entity) { * @return the iterable of updated entities with any id/key/rev saved in each * entity */ - // TODO refactor once template.upsert() is implemented + @SuppressWarnings("deprecation") @Override public Iterable save(final Iterable entities) { - arangoOperations.upsert(entities, UpsertStrategy.UPDATE); + if (arangoOperations.getVersion().getVersion().compareTo("3.4.0") < 0) { + arangoOperations.upsert(entities, UpsertStrategy.UPDATE); + } else { + final S first = StreamSupport.stream(entities.spliterator(), false).findFirst().get(); + arangoOperations.repsert(entities, (Class) first.getClass()); + } return entities; } From cac4a01676508c1250bbc62b2ec0190a3bc4cacf Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 2 Jul 2018 14:54:05 +0200 Subject: [PATCH 73/94] Fix ArangoOperations#upsert(T, UpsertStrategy) to work with @Id in addition to @Key --- .../core/template/ArangoTemplate.java | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) 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 0da56a173..8322b0c3b 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -30,6 +30,7 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; @@ -520,23 +521,23 @@ public DocumentEntity insert(final String collectionName, final Object value) th public void upsert(final T value, final UpsertStrategy strategy) throws DataAccessException { final Class entityClass = value.getClass(); final ArangoPersistentEntity entity = getConverter().getMappingContext().getPersistentEntity(entityClass); - final ArangoPersistentProperty idProperty = entity.getIdProperty(); - if (idProperty != null) { - final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(value), - converter.getConversionService()); - final Object id = accessor.getProperty(idProperty); - if (id != null) { - switch (strategy) { - case UPDATE: - update(id.toString(), value); - break; - case REPLACE: - default: - replace(id.toString(), value); - break; - } - return; + + final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(value), + converter.getConversionService()); + final Object id = entity.getKeyProperty().map(property -> accessor.getProperty(property)).orElseGet(() -> { + return entity.getIdProperty() != null ? accessor.getProperty(entity.getIdProperty()) : null; + }); + if (id != null) { + switch (strategy) { + case UPDATE: + update(id.toString(), value); + break; + case REPLACE: + default: + replace(id.toString(), value); + break; } + return; } insert(value); } @@ -544,21 +545,24 @@ public void upsert(final T value, final UpsertStrategy strategy) throws Data @SuppressWarnings("unchecked") @Override public void upsert(final Iterable value, final UpsertStrategy strategy) throws DataAccessException { - Class entityClass = null; - ArangoPersistentEntity entity = null; - ArangoPersistentProperty idProperty = null; + final Optional first = StreamSupport.stream(value.spliterator(), false).findFirst(); + if (!first.isPresent()) { + return; + } + final Class entityClass = (Class) first.get().getClass(); + final ArangoPersistentEntity entity = getConverter().getMappingContext().getPersistentEntity(entityClass); + final ArangoPersistentProperty idProperty = entity.getIdProperty(); + final Optional keyProperty = entity.getKeyProperty(); + final Collection withId = new ArrayList<>(); final Collection withoutId = new ArrayList<>(); for (final T e : value) { - if (entityClass == null) { - entityClass = (Class) e.getClass(); - entity = getConverter().getMappingContext().getPersistentEntity(entityClass); - idProperty = entity.getIdProperty(); - } - if (idProperty != null) { + if (keyProperty.isPresent() || idProperty != null) { final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(e), converter.getConversionService()); - final Object id = accessor.getProperty(idProperty); + final Object id = keyProperty.map(property -> accessor.getProperty(property)).orElseGet(() -> { + return idProperty != null ? accessor.getProperty(entity.getIdProperty()) : null; + }); if (id != null) { withId.add(e); continue; From da806f465acb910179da8ee30b8dec276b6e4185 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 2 Jul 2018 15:02:35 +0200 Subject: [PATCH 74/94] Refactor --- .../springframework/core/template/ArangoTemplate.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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 8322b0c3b..15c85926b 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -34,7 +34,7 @@ import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; -import org.springframework.data.mapping.model.ConvertingPropertyAccessor; +import org.springframework.data.mapping.PersistentPropertyAccessor; import com.arangodb.ArangoCollection; import com.arangodb.ArangoCursor; @@ -522,8 +522,7 @@ public void upsert(final T value, final UpsertStrategy strategy) throws Data final Class entityClass = value.getClass(); final ArangoPersistentEntity entity = getConverter().getMappingContext().getPersistentEntity(entityClass); - final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(value), - converter.getConversionService()); + final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(value); final Object id = entity.getKeyProperty().map(property -> accessor.getProperty(property)).orElseGet(() -> { return entity.getIdProperty() != null ? accessor.getProperty(entity.getIdProperty()) : null; }); @@ -558,8 +557,7 @@ public void upsert(final Iterable value, final UpsertStrategy strategy) t final Collection withoutId = new ArrayList<>(); for (final T e : value) { if (keyProperty.isPresent() || idProperty != null) { - final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(e), - converter.getConversionService()); + final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(e); final Object id = keyProperty.map(property -> accessor.getProperty(property)).orElseGet(() -> { return idProperty != null ? accessor.getProperty(entity.getIdProperty()) : null; }); @@ -617,8 +615,7 @@ public void repsert(final Iterable value, final Class entityClass) thr private void updateDBFields(final Object value, final DocumentEntity documentEntity) { final ArangoPersistentEntity entity = converter.getMappingContext().getPersistentEntity(value.getClass()); - final ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(value), - converter.getConversionService()); + final PersistentPropertyAccessor accessor = entity.getPropertyAccessor(value); final ArangoPersistentProperty idProperty = entity.getIdProperty(); if (idProperty != null) { accessor.setProperty(idProperty, documentEntity.getId()); From 7976953c4689031d53eb4800eb1eafe09f725d33 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 2 Jul 2018 15:07:38 +0200 Subject: [PATCH 75/94] Update ChangeLog --- ChangeLog.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index e003da925..12c963317 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Added +- added `ArangoOperations#repsert(T)` +- added `ArangoOperations#repsert(Iterable, Class)` - added support for streaming AQL cursors - added `QueryOptions#stream()` - added `QueryOptions#memoryLimit()` @@ -18,6 +20,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## Changed - upgraded dependency arangodb-java-driver 4.6.0 +- changed `SimpleArangoRepository#save()` to use `ArangoOperations#repsert()` when ArangoDB version >= 3.4.0 +- changed `SimpleArangoRepository#saveAll()` to use `ArangoOperations#repsert()` when ArangoDB version >= 3.4.0 +- changed `ArangoOperations#upsert(T, UpsertStrategy)` to work with `@Id` in addition to `@Key` +- changed `ArangoOperations#upsert(Iterable, UpsertStrategy)` to work with `@Id` in addition to `@Key` + +### Deprecated + +- deprecated `ArangoOperations#upsert(T, UpsertStrategy)` +- deprecated `ArangoOperations#upsert(Iterable, UpsertStrategy)` ## [1.1.8] - 2018-06-26 From ec58f0d526d88c1d500df96c9c8c995f01b50d49 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 2 Jul 2018 15:51:49 +0200 Subject: [PATCH 76/94] Prepare release 1.2.0 --- ChangeLog.md | 2 ++ pom.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 12c963317..aa4f686a8 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [1.2.0] - 2018-07-02 + ### Added - added `ArangoOperations#repsert(T)` diff --git a/pom.xml b/pom.xml index 36fb8a4d9..02a03154b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.1.9-SNAPSHOT + 1.2.0 2017 jar From 4a7b6d5be10d7f5df7afc78fdb04ad4c4b609274 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 2 Jul 2018 16:18:56 +0200 Subject: [PATCH 77/94] Prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 02a03154b..d8f5c09ef 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.2.0 + 1.2.1-SNAPSHOT 2017 jar From 0f9701e801339d3269e3e275ec9aa9921f1aa9af Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 3 Jul 2018 09:37:16 +0200 Subject: [PATCH 78/94] Fix ArangoOperations#upsert (issue #92) --- ChangeLog.md | 7 +++ .../core/template/ArangoTemplate.java | 5 +- .../core/template/ArangoTemplateTest.java | 57 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index aa4f686a8..903edd68f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Fixed + +- fixed `ArangoOperations#upsert(T, UpsertStrategy)` (issue #92) + - Check `Persistable#isNew` +- fixed `ArangoOperations#upsert(Iterable, UpsertStrategy)` (issue #92) + - Check `Persistable#isNew` + ## [1.2.0] - 2018-07-02 ### Added 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 15c85926b..b1fef4d89 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -34,6 +34,7 @@ import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; +import org.springframework.data.domain.Persistable; import org.springframework.data.mapping.PersistentPropertyAccessor; import com.arangodb.ArangoCollection; @@ -526,7 +527,7 @@ public void upsert(final T value, final UpsertStrategy strategy) throws Data final Object id = entity.getKeyProperty().map(property -> accessor.getProperty(property)).orElseGet(() -> { return entity.getIdProperty() != null ? accessor.getProperty(entity.getIdProperty()) : null; }); - if (id != null) { + if (id != null && (!(value instanceof Persistable) || !Persistable.class.cast(value).isNew())) { switch (strategy) { case UPDATE: update(id.toString(), value); @@ -561,7 +562,7 @@ public void upsert(final Iterable value, final UpsertStrategy strategy) t final Object id = keyProperty.map(property -> accessor.getProperty(property)).orElseGet(() -> { return idProperty != null ? accessor.getProperty(entity.getIdProperty()) : null; }); - if (id != null) { + if (id != null && (!(e instanceof Persistable) || !Persistable.class.cast(e).isNew())) { withId.add(e); continue; } diff --git a/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java b/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java index 04027a8d7..4e13cdb4e 100644 --- a/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java +++ b/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java @@ -34,6 +34,9 @@ import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Transient; +import org.springframework.data.domain.Persistable; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -45,6 +48,7 @@ import com.arangodb.model.AqlQueryOptions; import com.arangodb.springframework.AbstractArangoTest; import com.arangodb.springframework.ArangoTestConfiguration; +import com.arangodb.springframework.annotation.Key; import com.arangodb.springframework.core.ArangoOperations.UpsertStrategy; import com.arangodb.springframework.testdata.Address; import com.arangodb.springframework.testdata.Customer; @@ -349,4 +353,57 @@ public void queryVPackSlice() { assertThat(customers.get(0).get("age").getAsInt(), is(30)); } + public static class NewEntityTest implements Persistable { + + @Id + private String id; + @Key + private final String key; + private transient boolean persisted; + + public NewEntityTest(final String key) { + super(); + this.key = key; + } + + public void setPersisted(final boolean persisted) { + this.persisted = persisted; + } + + @Override + @Transient + public boolean isNew() { + return !persisted; + } + + @Override + public String getId() { + return key; + } + } + + @SuppressWarnings("deprecation") + @Test + public void upsertWithUserGeneratedKey() { + final NewEntityTest entity = new NewEntityTest("test"); + template.upsert(entity, UpsertStrategy.REPLACE); + assertThat(template.collection(NewEntityTest.class).count(), is(1L)); + entity.setPersisted(true); + template.upsert(entity, UpsertStrategy.REPLACE); + assertThat(template.collection(NewEntityTest.class).count(), is(1L)); + } + + @SuppressWarnings("deprecation") + @Test + public void mutliUpsertWithUserGeneratedKey() { + final NewEntityTest entity1 = new NewEntityTest("test1"); + final NewEntityTest entity2 = new NewEntityTest("test2"); + template.upsert(Arrays.asList(entity1, entity2), UpsertStrategy.REPLACE); + assertThat(template.collection(NewEntityTest.class).count(), is(2L)); + entity1.setPersisted(true); + entity2.setPersisted(true); + template.upsert(Arrays.asList(entity1, entity2), UpsertStrategy.REPLACE); + assertThat(template.collection(NewEntityTest.class).count(), is(2L)); + } + } From e15149391e1c10514dbd01a8cbcf3cdd62b67547 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 3 Jul 2018 09:44:34 +0200 Subject: [PATCH 79/94] Fix test --- .../springframework/core/template/ArangoTemplateTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java b/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java index 4e13cdb4e..559ea2427 100644 --- a/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java +++ b/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java @@ -359,6 +359,7 @@ public static class NewEntityTest implements Persistable { private String id; @Key private final String key; + @Transient private transient boolean persisted; public NewEntityTest(final String key) { @@ -371,7 +372,6 @@ public void setPersisted(final boolean persisted) { } @Override - @Transient public boolean isNew() { return !persisted; } From 53de95560e34e1c0031cc331e09fded9c3adf035 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 3 Jul 2018 10:26:03 +0200 Subject: [PATCH 80/94] Prepare release 1.2.1 --- ChangeLog.md | 2 ++ pom.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 903edd68f..fea582ce9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [1.2.1] - 2018-07-03 + ### Fixed - fixed `ArangoOperations#upsert(T, UpsertStrategy)` (issue #92) diff --git a/pom.xml b/pom.xml index d8f5c09ef..59c253b67 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.2.1-SNAPSHOT + 1.2.1 2017 jar From 4c1ed6750ddee575b824365c746b99b272e751b8 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Thu, 5 Jul 2018 14:56:57 +0200 Subject: [PATCH 81/94] Fix `ArangoOperations#getVersion()` use configured database instead of _system --- ChangeLog.md | 1 + .../arangodb/springframework/core/template/ArangoTemplate.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index fea582ce9..76cf5717b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Check `Persistable#isNew` - fixed `ArangoOperations#upsert(Iterable, UpsertStrategy)` (issue #92) - Check `Persistable#isNew` +- fixed `ArangoOperations#getVersion()` use configured database instead of _system ## [1.2.0] - 2018-07-02 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 b1fef4d89..2c83c36f1 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -289,7 +289,7 @@ public ArangoDB driver() { public ArangoDBVersion getVersion() throws DataAccessException { try { if (version == null) { - version = arango.getVersion(); + version = db().getVersion(); } return version; } catch (final ArangoDBException e) { From ab5497e285d3b89f0a1d9d62c19cc9a99a408e78 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 9 Jul 2018 14:21:21 +0200 Subject: [PATCH 82/94] Prepare release 1.2.2 --- ChangeLog.md | 5 ++++- pom.xml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 76cf5717b..f6148d97f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [1.2.2] - 2018-07-09 + +- fixed `ArangoOperations#getVersion()` use configured database instead of \_system + ## [1.2.1] - 2018-07-03 ### Fixed @@ -14,7 +18,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a - Check `Persistable#isNew` - fixed `ArangoOperations#upsert(Iterable, UpsertStrategy)` (issue #92) - Check `Persistable#isNew` -- fixed `ArangoOperations#getVersion()` use configured database instead of _system ## [1.2.0] - 2018-07-02 diff --git a/pom.xml b/pom.xml index 59c253b67..a082d6204 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.2.1 + 1.2.2 2017 jar From cbfbcc85f176730059ebd54b65cba3e69d105edb Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 17 Jul 2018 11:24:28 +0200 Subject: [PATCH 83/94] Deprecate Key, DBEntity, DBCollectionEntity - deprecated `com.arangodb.springframework.annotation.Key` - deprecated `com.arangodb.springframework.core.convert.DBEntity` - deprecated `com.arangodb.springframework.core.convert.DBCollectionEntity` --- ChangeLog.md | 6 + pom.xml | 2 +- .../springframework/annotation/Key.java | 75 +- .../springframework/annotation/Param.java | 2 +- .../core/convert/ArangoConverter.java | 87 +- .../core/convert/ArangoEntityReader.java | 63 +- .../core/convert/ArangoEntityWriter.java | 63 +- .../core/convert/ArangoTypeMapper.java | 67 +- .../core/convert/DBCollectionEntity.java | 106 +-- .../core/convert/DBDocumentEntity.java | 99 ++- .../core/convert/DBEntity.java | 74 +- .../core/convert/DBEntityDeserializer.java | 87 +- .../core/convert/DBEntityModule.java | 123 +-- .../core/convert/DefaultArangoConverter.java | 1 + .../core/convert/DefaultArangoTypeMapper.java | 209 ++--- .../core/mapping/ArangoSimpleTypes.java | 221 ++--- .../DefaultArangoPersistentProperty.java | 297 +++---- .../template/ArangoExtCursorIterator.java | 179 ++-- .../core/template/ArangoTemplate.java | 115 ++- .../core/mapping/ArangoMappingTest.java | 74 +- .../core/template/ArangoTemplateTest.java | 819 +++++++++--------- .../springframework/testdata/Customer.java | 495 +++++------ .../testdata/IncompleteCustomer.java | 1 + .../springframework/testdata/Material.java | 36 +- .../springframework/testdata/Product.java | 265 +++--- 25 files changed, 1818 insertions(+), 1748 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index f6148d97f..839c1f636 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Deprecated + +- deprecated `com.arangodb.springframework.annotation.Key` +- deprecated `com.arangodb.springframework.core.convert.DBEntity` +- deprecated `com.arangodb.springframework.core.convert.DBCollectionEntity` + ## [1.2.2] - 2018-07-09 - fixed `ArangoOperations#getVersion()` use configured database instead of \_system diff --git a/pom.xml b/pom.xml index a082d6204..d6c6acbbc 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.2.2 + 1.3.0-SNAPSHOT 2017 jar diff --git a/src/main/java/com/arangodb/springframework/annotation/Key.java b/src/main/java/com/arangodb/springframework/annotation/Key.java index e2bb130f9..10cfaddc7 100644 --- a/src/main/java/com/arangodb/springframework/annotation/Key.java +++ b/src/main/java/com/arangodb/springframework/annotation/Key.java @@ -1,36 +1,39 @@ -/* - * 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; - -/** - * @author Mark Vollmary - * - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.FIELD }) -public @interface Key { - -} +/* + * 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; + +/** + * @deprecated Represents the document field {@code _key}. Will be removed in 3.0.0, please use + * {@link org.springframework.data.annotation.Id} then instead. + * + * @author Mark Vollmary + */ +@Deprecated +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.FIELD }) +public @interface Key { + +} diff --git a/src/main/java/com/arangodb/springframework/annotation/Param.java b/src/main/java/com/arangodb/springframework/annotation/Param.java index 0384b78b6..36208a546 100644 --- a/src/main/java/com/arangodb/springframework/annotation/Param.java +++ b/src/main/java/com/arangodb/springframework/annotation/Param.java @@ -26,7 +26,7 @@ import java.lang.annotation.Target; /** - * Deprecated. Please use {@link org.springframework.data.repository.query.Param} instead. + * @deprecated. Will be removed in 3.0.0, please use {@link org.springframework.data.repository.query.Param} instead. * * @author Audrius Malele * @author Mark McCormick diff --git a/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java b/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java index 9ed20e331..3f09042c2 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/ArangoConverter.java @@ -1,43 +1,44 @@ -/* - * 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.core.convert; - -import org.springframework.data.convert.EntityConverter; - -import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; -import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; - -/** - * @author Mark Vollmary - * @author Christian Lechner - * - */ -public interface ArangoConverter - extends EntityConverter, ArangoPersistentProperty, Object, DBEntity>, - ArangoEntityReader, ArangoEntityWriter { - - boolean isCollectionType(Class type); - - boolean isEntityType(Class type); - - ArangoTypeMapper getTypeMapper(); - -} +/* + * 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.core.convert; + +import org.springframework.data.convert.EntityConverter; + +import com.arangodb.springframework.core.mapping.ArangoPersistentEntity; +import com.arangodb.springframework.core.mapping.ArangoPersistentProperty; + +/** + * @author Mark Vollmary + * @author Christian Lechner + * + */ +@SuppressWarnings("deprecation") +public interface ArangoConverter + extends EntityConverter, ArangoPersistentProperty, Object, DBEntity>, + ArangoEntityReader, ArangoEntityWriter { + + boolean isCollectionType(Class type); + + boolean isEntityType(Class type); + + ArangoTypeMapper getTypeMapper(); + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/ArangoEntityReader.java b/src/main/java/com/arangodb/springframework/core/convert/ArangoEntityReader.java index 19c1b0a54..98be945d4 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/ArangoEntityReader.java +++ b/src/main/java/com/arangodb/springframework/core/convert/ArangoEntityReader.java @@ -1,31 +1,32 @@ -/* - * 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.core.convert; - -import org.springframework.data.convert.EntityReader; - -/** - * @author Mark Vollmary - * - */ -public interface ArangoEntityReader extends EntityReader { - -} +/* + * 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.core.convert; + +import org.springframework.data.convert.EntityReader; + +/** + * @author Mark Vollmary + * + */ +@SuppressWarnings("deprecation") +public interface ArangoEntityReader extends EntityReader { + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/ArangoEntityWriter.java b/src/main/java/com/arangodb/springframework/core/convert/ArangoEntityWriter.java index 0adde9b2f..5ebb92b8a 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/ArangoEntityWriter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/ArangoEntityWriter.java @@ -1,31 +1,32 @@ -/* - * 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.core.convert; - -import org.springframework.data.convert.EntityWriter; - -/** - * @author Mark Vollmary - * - */ -public interface ArangoEntityWriter extends EntityWriter { - -} +/* + * 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.core.convert; + +import org.springframework.data.convert.EntityWriter; + +/** + * @author Mark Vollmary + * + */ +@SuppressWarnings("deprecation") +public interface ArangoEntityWriter extends EntityWriter { + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/ArangoTypeMapper.java b/src/main/java/com/arangodb/springframework/core/convert/ArangoTypeMapper.java index 942a9b2b4..a82caadd2 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/ArangoTypeMapper.java +++ b/src/main/java/com/arangodb/springframework/core/convert/ArangoTypeMapper.java @@ -1,33 +1,34 @@ -/* - * 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.convert; - -import org.springframework.data.convert.TypeMapper; - -/** - * @author Christian Lechner - * - */ -public interface ArangoTypeMapper extends TypeMapper { - - boolean isTypeKey(String key); - -} +/* + * 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.convert; + +import org.springframework.data.convert.TypeMapper; + +/** + * @author Christian Lechner + * + */ +@SuppressWarnings("deprecation") +public interface ArangoTypeMapper extends TypeMapper { + + boolean isTypeKey(String key); + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/DBCollectionEntity.java b/src/main/java/com/arangodb/springframework/core/convert/DBCollectionEntity.java index ba8194807..ab28adc43 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DBCollectionEntity.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DBCollectionEntity.java @@ -1,52 +1,54 @@ -/* - * 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.core.convert; - -import java.util.ArrayList; -import java.util.Collection; - -/** - * @author Mark Vollmary - * - */ -public class DBCollectionEntity extends ArrayList implements DBEntity { - - private static final long serialVersionUID = -2068955559598596722L; - - public DBCollectionEntity() { - super(); - } - - public DBCollectionEntity(final Collection c) { - super(c); - } - - @Override - public Object put(final String key, final Object value) { - throw new UnsupportedOperationException(); - } - - @Override - public Object get(final Object key) { - throw new UnsupportedOperationException(); - } - -} +/* + * 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.core.convert; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * @deprecated Will be removed in 3.0.0 + * + * @author Mark Vollmary + */ +@Deprecated +public class DBCollectionEntity extends ArrayList implements DBEntity { + + private static final long serialVersionUID = -2068955559598596722L; + + public DBCollectionEntity() { + super(); + } + + public DBCollectionEntity(final Collection c) { + super(c); + } + + @Override + public Object put(final String key, final Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object get(final Object key) { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/DBDocumentEntity.java b/src/main/java/com/arangodb/springframework/core/convert/DBDocumentEntity.java index 0f2a3f062..f8d4b7c9b 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DBDocumentEntity.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DBDocumentEntity.java @@ -1,47 +1,52 @@ -/* - * 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.core.convert; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author Mark Vollmary - * - */ -public class DBDocumentEntity extends HashMap implements DBEntity { - - private static final long serialVersionUID = -7251842887063588024L; - - public DBDocumentEntity() { - super(); - } - - public DBDocumentEntity(final Map m) { - super(m); - } - - @Override - public boolean add(final Object value) { - throw new UnsupportedOperationException(); - } - -} +/* + * 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.core.convert; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Mark Vollmary + * + */ +@SuppressWarnings("deprecation") +public class DBDocumentEntity extends HashMap implements DBEntity { + + private static final long serialVersionUID = -7251842887063588024L; + + public DBDocumentEntity() { + super(); + } + + public DBDocumentEntity(final Map m) { + super(m); + } + + /** + * @deprecated Will be removed in 3.0.0 + */ + @Override + @Deprecated + public boolean add(final Object value) { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/DBEntity.java b/src/main/java/com/arangodb/springframework/core/convert/DBEntity.java index 2107750f4..696f6dd6c 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DBEntity.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DBEntity.java @@ -1,35 +1,39 @@ -/* - * 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.core.convert; - -/** - * @author Mark Vollmary - * - */ -public interface DBEntity { - - Object put(String key, Object value); - - Object get(Object key); - - boolean add(Object value); - -} +/* + * 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.core.convert; + +import com.arangodb.entity.DocumentEntity; + +/** + * @deprecated Will be removed in 3.0.0, please use {@link DocumentEntity} instead + * + * @author Mark Vollmary + */ +@Deprecated +public interface DBEntity { + + Object put(String key, Object value); + + Object get(Object key); + + boolean add(Object value); + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/DBEntityDeserializer.java b/src/main/java/com/arangodb/springframework/core/convert/DBEntityDeserializer.java index 131d0e7c0..f2dad0587 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DBEntityDeserializer.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DBEntityDeserializer.java @@ -1,43 +1,44 @@ -/* - * 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.core.convert; - -import com.arangodb.velocypack.VPackDeserializationContext; -import com.arangodb.velocypack.VPackDeserializer; -import com.arangodb.velocypack.VPackSlice; -import com.arangodb.velocypack.exception.VPackException; - -/** - * @author Mark Vollmary - * - */ -public class DBEntityDeserializer implements VPackDeserializer { - - @Override - public DBEntity deserialize( - final VPackSlice parent, - final VPackSlice vpack, - final VPackDeserializationContext context) throws VPackException { - final Class type = vpack.isObject() ? DBDocumentEntity.class : DBCollectionEntity.class; - return (DBEntity) context.deserialize(vpack, type); - } - -} +/* + * 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.core.convert; + +import com.arangodb.velocypack.VPackDeserializationContext; +import com.arangodb.velocypack.VPackDeserializer; +import com.arangodb.velocypack.VPackSlice; +import com.arangodb.velocypack.exception.VPackException; + +/** + * @author Mark Vollmary + * + */ +@SuppressWarnings("deprecation") +public class DBEntityDeserializer implements VPackDeserializer { + + @Override + public DBEntity deserialize( + final VPackSlice parent, + final VPackSlice vpack, + final VPackDeserializationContext context) throws VPackException { + final Class type = vpack.isObject() ? DBDocumentEntity.class : DBCollectionEntity.class; + return (DBEntity) context.deserialize(vpack, type); + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/DBEntityModule.java b/src/main/java/com/arangodb/springframework/core/convert/DBEntityModule.java index 907a3c653..9d4ba45d0 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DBEntityModule.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DBEntityModule.java @@ -1,61 +1,62 @@ -/* - * 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.convert; - -import java.util.Collection; -import java.util.Map; - -import com.arangodb.velocypack.VPackInstanceCreator; -import com.arangodb.velocypack.VPackModule; -import com.arangodb.velocypack.VPackSetupContext; - -/** - * @author Christian Lechner - * - */ -public class DBEntityModule implements VPackModule { - - @Override - public > void setup(C context) { - context.registerInstanceCreator(Map.class, new DBDocumentEntityInstantiator()) - .registerInstanceCreator(Collection.class, new DBCollectionEntityInstantiator()) - .registerDeserializer(DBEntity.class, new DBEntityDeserializer()); - } - - public static class DBDocumentEntityInstantiator implements VPackInstanceCreator> { - - @Override - public Map createInstance() { - return new DBDocumentEntity(); - } - - } - - public static class DBCollectionEntityInstantiator implements VPackInstanceCreator> { - - @Override - public Collection createInstance() { - return new DBCollectionEntity(); - } - - } - -} +/* + * 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.convert; + +import java.util.Collection; +import java.util.Map; + +import com.arangodb.velocypack.VPackInstanceCreator; +import com.arangodb.velocypack.VPackModule; +import com.arangodb.velocypack.VPackSetupContext; + +/** + * @author Christian Lechner + * + */ +@SuppressWarnings("deprecation") +public class DBEntityModule implements VPackModule { + + @Override + public > void setup(final C context) { + context.registerInstanceCreator(Map.class, new DBDocumentEntityInstantiator()) + .registerInstanceCreator(Collection.class, new DBCollectionEntityInstantiator()) + .registerDeserializer(DBEntity.class, new DBEntityDeserializer()); + } + + public static class DBDocumentEntityInstantiator implements VPackInstanceCreator> { + + @Override + public Map createInstance() { + return new DBDocumentEntity(); + } + + } + + public static class DBCollectionEntityInstantiator implements VPackInstanceCreator> { + + @Override + public Collection createInstance() { + return new DBCollectionEntity(); + } + + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java index 5175bbabd..71a97aff1 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java @@ -59,6 +59,7 @@ * @author Christian Lechner * */ +@SuppressWarnings("deprecation") public class DefaultArangoConverter implements ArangoConverter { private static final String _ID = "_id"; diff --git a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoTypeMapper.java b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoTypeMapper.java index 8fff5f33a..651a910ec 100644 --- a/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoTypeMapper.java +++ b/src/main/java/com/arangodb/springframework/core/convert/DefaultArangoTypeMapper.java @@ -1,104 +1,105 @@ -/* - * 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.convert; - -import java.util.Arrays; -import java.util.List; - -import org.springframework.data.convert.DefaultTypeMapper; -import org.springframework.data.convert.SimpleTypeInformationMapper; -import org.springframework.data.convert.TypeAliasAccessor; -import org.springframework.data.convert.TypeInformationMapper; -import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.mapping.context.MappingContext; - -/** - * @author Christian Lechner - * - */ -public class DefaultArangoTypeMapper extends DefaultTypeMapper implements ArangoTypeMapper { - - public static final String DEFAULT_TYPE_KEY = "_class"; - - private final String typeKey; - - public DefaultArangoTypeMapper() { - this(DEFAULT_TYPE_KEY); - } - - public DefaultArangoTypeMapper(final String typeKey) { - this(typeKey, Arrays.asList(new SimpleTypeInformationMapper())); - } - - public DefaultArangoTypeMapper(final String typeKey, - final MappingContext, ?> mappingContext) { - this(typeKey, new DocumentTypeAliasAccessor(typeKey), mappingContext, - Arrays.asList(new SimpleTypeInformationMapper())); - } - - public DefaultArangoTypeMapper(final String typeKey, final List mappers) { - this(typeKey, new DocumentTypeAliasAccessor(typeKey), null, mappers); - } - - private DefaultArangoTypeMapper(final String typeKey, final TypeAliasAccessor accessor, - final MappingContext, ?> mappingContext, - final List mappers) { - - super(accessor, mappingContext, mappers); - this.typeKey = typeKey; - } - - @Override - public boolean isTypeKey(final String key) { - return typeKey == null ? false : typeKey.equals(key); - } - - public static final class DocumentTypeAliasAccessor implements TypeAliasAccessor { - - private final String typeKey; - - public DocumentTypeAliasAccessor(final String typeKey) { - this.typeKey = typeKey; - } - - @Override - public Object readAliasFrom(final DBEntity source) { - if (source instanceof DBCollectionEntity) { - return null; - } - - if (source instanceof DBDocumentEntity) { - return source.get(this.typeKey); - } - - throw new IllegalArgumentException("Cannot read alias from " + source.getClass()); - } - - @Override - public void writeTypeTo(final DBEntity sink, final Object alias) { - if (this.typeKey != null && sink instanceof DBDocumentEntity) { - sink.put(this.typeKey, alias); - } - } - - } - -} +/* + * 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.convert; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.data.convert.DefaultTypeMapper; +import org.springframework.data.convert.SimpleTypeInformationMapper; +import org.springframework.data.convert.TypeAliasAccessor; +import org.springframework.data.convert.TypeInformationMapper; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.context.MappingContext; + +/** + * @author Christian Lechner + * + */ +@SuppressWarnings("deprecation") +public class DefaultArangoTypeMapper extends DefaultTypeMapper implements ArangoTypeMapper { + + public static final String DEFAULT_TYPE_KEY = "_class"; + + private final String typeKey; + + public DefaultArangoTypeMapper() { + this(DEFAULT_TYPE_KEY); + } + + public DefaultArangoTypeMapper(final String typeKey) { + this(typeKey, Arrays.asList(new SimpleTypeInformationMapper())); + } + + public DefaultArangoTypeMapper(final String typeKey, + final MappingContext, ?> mappingContext) { + this(typeKey, new DocumentTypeAliasAccessor(typeKey), mappingContext, + Arrays.asList(new SimpleTypeInformationMapper())); + } + + public DefaultArangoTypeMapper(final String typeKey, final List mappers) { + this(typeKey, new DocumentTypeAliasAccessor(typeKey), null, mappers); + } + + private DefaultArangoTypeMapper(final String typeKey, final TypeAliasAccessor accessor, + final MappingContext, ?> mappingContext, + final List mappers) { + + super(accessor, mappingContext, mappers); + this.typeKey = typeKey; + } + + @Override + public boolean isTypeKey(final String key) { + return typeKey == null ? false : typeKey.equals(key); + } + + public static final class DocumentTypeAliasAccessor implements TypeAliasAccessor { + + private final String typeKey; + + public DocumentTypeAliasAccessor(final String typeKey) { + this.typeKey = typeKey; + } + + @Override + public Object readAliasFrom(final DBEntity source) { + if (source instanceof DBCollectionEntity) { + return null; + } + + if (source instanceof DBDocumentEntity) { + return source.get(this.typeKey); + } + + throw new IllegalArgumentException("Cannot read alias from " + source.getClass()); + } + + @Override + public void writeTypeTo(final DBEntity sink, final Object alias) { + if (this.typeKey != null && sink instanceof DBDocumentEntity) { + sink.put(this.typeKey, alias); + } + } + + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java b/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java index 4d9d649b8..3cee178b2 100644 --- a/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java +++ b/src/main/java/com/arangodb/springframework/core/mapping/ArangoSimpleTypes.java @@ -1,111 +1,112 @@ -/* - * 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.mapping; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.sql.Timestamp; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - -import org.springframework.data.mapping.model.SimpleTypeHolder; - -import com.arangodb.entity.BaseDocument; -import com.arangodb.entity.BaseEdgeDocument; -import com.arangodb.springframework.core.convert.DBEntity; -import com.arangodb.velocypack.VPackSlice; - -/** - * This class contains all types that are directly supported by the Java driver (through java-velocypack). - * - * @author Christian Lechner - * - */ -public abstract class ArangoSimpleTypes { - - private static final Set> ARANGO_SIMPLE_TYPES; - - static { - final Set> simpleTypes = new HashSet<>(); - - // com.arangodb.* - simpleTypes.add(DBEntity.class); - simpleTypes.add(BaseDocument.class); - simpleTypes.add(BaseEdgeDocument.class); - simpleTypes.add(VPackSlice.class); - - // primitives - simpleTypes.add(boolean.class); - simpleTypes.add(byte.class); - simpleTypes.add(char.class); - simpleTypes.add(short.class); - simpleTypes.add(int.class); - simpleTypes.add(long.class); - simpleTypes.add(float.class); - simpleTypes.add(double.class); - - // primitive arrays - simpleTypes.add(boolean[].class); - simpleTypes.add(byte[].class); - simpleTypes.add(char[].class); - simpleTypes.add(short[].class); - simpleTypes.add(int[].class); - simpleTypes.add(long[].class); - simpleTypes.add(float[].class); - simpleTypes.add(double[].class); - - // java.lang.* - simpleTypes.add(Boolean.class); - simpleTypes.add(Byte.class); - simpleTypes.add(Character.class); - simpleTypes.add(Short.class); - simpleTypes.add(Integer.class); - simpleTypes.add(Long.class); - simpleTypes.add(Float.class); - simpleTypes.add(Double.class); - simpleTypes.add(Number.class); - simpleTypes.add(String.class); - simpleTypes.add(Enum.class); - - // java.math.* - simpleTypes.add(BigInteger.class); - simpleTypes.add(BigDecimal.class); - - // java.util.* - simpleTypes.add(UUID.class); - simpleTypes.add(Date.class); - - // java.sql.* - simpleTypes.add(java.sql.Date.class); - simpleTypes.add(Timestamp.class); - - ARANGO_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes); - } - - public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(ARANGO_SIMPLE_TYPES, false); - - private ArangoSimpleTypes() { - } - +/* + * 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.mapping; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Timestamp; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +import org.springframework.data.mapping.model.SimpleTypeHolder; + +import com.arangodb.entity.BaseDocument; +import com.arangodb.entity.BaseEdgeDocument; +import com.arangodb.springframework.core.convert.DBEntity; +import com.arangodb.velocypack.VPackSlice; + +/** + * This class contains all types that are directly supported by the Java driver (through java-velocypack). + * + * @author Christian Lechner + * + */ +@SuppressWarnings("deprecation") +public abstract class ArangoSimpleTypes { + + private static final Set> ARANGO_SIMPLE_TYPES; + + static { + final Set> simpleTypes = new HashSet<>(); + + // com.arangodb.* + simpleTypes.add(DBEntity.class); + simpleTypes.add(BaseDocument.class); + simpleTypes.add(BaseEdgeDocument.class); + simpleTypes.add(VPackSlice.class); + + // primitives + simpleTypes.add(boolean.class); + simpleTypes.add(byte.class); + simpleTypes.add(char.class); + simpleTypes.add(short.class); + simpleTypes.add(int.class); + simpleTypes.add(long.class); + simpleTypes.add(float.class); + simpleTypes.add(double.class); + + // primitive arrays + simpleTypes.add(boolean[].class); + simpleTypes.add(byte[].class); + simpleTypes.add(char[].class); + simpleTypes.add(short[].class); + simpleTypes.add(int[].class); + simpleTypes.add(long[].class); + simpleTypes.add(float[].class); + simpleTypes.add(double[].class); + + // java.lang.* + simpleTypes.add(Boolean.class); + simpleTypes.add(Byte.class); + simpleTypes.add(Character.class); + simpleTypes.add(Short.class); + simpleTypes.add(Integer.class); + simpleTypes.add(Long.class); + simpleTypes.add(Float.class); + simpleTypes.add(Double.class); + simpleTypes.add(Number.class); + simpleTypes.add(String.class); + simpleTypes.add(Enum.class); + + // java.math.* + simpleTypes.add(BigInteger.class); + simpleTypes.add(BigDecimal.class); + + // java.util.* + simpleTypes.add(UUID.class); + simpleTypes.add(Date.class); + + // java.sql.* + simpleTypes.add(java.sql.Date.class); + simpleTypes.add(Timestamp.class); + + ARANGO_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes); + } + + public static final SimpleTypeHolder HOLDER = new SimpleTypeHolder(ARANGO_SIMPLE_TYPES, false); + + private ArangoSimpleTypes() { + } + } \ No newline at end of file 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 02af1c38d..1812c61c3 100644 --- a/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentProperty.java +++ b/src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentProperty.java @@ -1,148 +1,149 @@ -/* - * 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.core.mapping; - -import java.beans.PropertyDescriptor; -import java.util.Optional; - -import org.springframework.data.mapping.Association; -import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; -import org.springframework.data.mapping.model.FieldNamingStrategy; -import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; -import org.springframework.data.mapping.model.SimpleTypeHolder; -import org.springframework.util.StringUtils; - -import com.arangodb.springframework.annotation.Field; -import com.arangodb.springframework.annotation.From; -import com.arangodb.springframework.annotation.FulltextIndexed; -import com.arangodb.springframework.annotation.GeoIndexed; -import com.arangodb.springframework.annotation.HashIndexed; -import com.arangodb.springframework.annotation.Key; -import com.arangodb.springframework.annotation.PersistentIndexed; -import com.arangodb.springframework.annotation.Ref; -import com.arangodb.springframework.annotation.Relations; -import com.arangodb.springframework.annotation.Rev; -import com.arangodb.springframework.annotation.SkiplistIndexed; -import com.arangodb.springframework.annotation.To; - -/** - * @author Mark Vollmary - * - */ -public class DefaultArangoPersistentProperty extends AnnotationBasedPersistentProperty - implements ArangoPersistentProperty { - - private final FieldNamingStrategy fieldNamingStrategy; - - public DefaultArangoPersistentProperty(final java.lang.reflect.Field field, - final PropertyDescriptor propertyDescriptor, final PersistentEntity owner, - final SimpleTypeHolder simpleTypeHolder, final FieldNamingStrategy fieldNamingStrategy) { - super(field, propertyDescriptor, owner, simpleTypeHolder); - this.fieldNamingStrategy = fieldNamingStrategy != null ? fieldNamingStrategy - : PropertyNameFieldNamingStrategy.INSTANCE; - } - - @Override - protected Association createAssociation() { - return new Association<>(this, null); - } - - @Override - public boolean isKeyProperty() { - return findAnnotation(Key.class) != null; - } - - @Override - public boolean isRevProperty() { - return findAnnotation(Rev.class) != null; - } - - @Override - public Optional getRef() { - return Optional.ofNullable(findAnnotation(Ref.class)); - } - - @Override - public Optional getRelations() { - return Optional.ofNullable(findAnnotation(Relations.class)); - } - - @Override - public Optional getFrom() { - return Optional.ofNullable(findAnnotation(From.class)); - } - - @Override - public Optional getTo() { - return Optional.ofNullable(findAnnotation(To.class)); - } - - @Override - public String getFieldName() { - final String fieldName; - if (isIdProperty()) { - fieldName = "_id"; - } else if (isKeyProperty()) { - fieldName = "_key"; - } else if (isRevProperty()) { - fieldName = "_rev"; - } else if (getFrom().isPresent()) { - fieldName = "_from"; - } else if (getTo().isPresent()) { - fieldName = "_to"; - } else { - fieldName = getAnnotatedFieldName().orElse(fieldNamingStrategy.getFieldName(this)); - } - return fieldName; - } - - private Optional getAnnotatedFieldName() { - return Optional.ofNullable(findAnnotation(Field.class)) - .map(f -> StringUtils.hasText(f.value()) ? f.value() : null); - } - - @Override - public Optional getHashIndexed() { - return Optional.ofNullable(findAnnotation(HashIndexed.class)); - } - - @Override - public Optional getSkiplistIndexed() { - return Optional.ofNullable(findAnnotation(SkiplistIndexed.class)); - } - - @Override - public Optional getPersistentIndexed() { - return Optional.ofNullable(findAnnotation(PersistentIndexed.class)); - } - - @Override - public Optional getGeoIndexed() { - return Optional.ofNullable(findAnnotation(GeoIndexed.class)); - } - - @Override - public Optional getFulltextIndexed() { - return Optional.ofNullable(findAnnotation(FulltextIndexed.class)); - } - -} +/* + * 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.core.mapping; + +import java.beans.PropertyDescriptor; +import java.util.Optional; + +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; +import org.springframework.data.mapping.model.FieldNamingStrategy; +import org.springframework.data.mapping.model.PropertyNameFieldNamingStrategy; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.util.StringUtils; + +import com.arangodb.springframework.annotation.Field; +import com.arangodb.springframework.annotation.From; +import com.arangodb.springframework.annotation.FulltextIndexed; +import com.arangodb.springframework.annotation.GeoIndexed; +import com.arangodb.springframework.annotation.HashIndexed; +import com.arangodb.springframework.annotation.Key; +import com.arangodb.springframework.annotation.PersistentIndexed; +import com.arangodb.springframework.annotation.Ref; +import com.arangodb.springframework.annotation.Relations; +import com.arangodb.springframework.annotation.Rev; +import com.arangodb.springframework.annotation.SkiplistIndexed; +import com.arangodb.springframework.annotation.To; + +/** + * @author Mark Vollmary + * + */ +@SuppressWarnings("deprecation") +public class DefaultArangoPersistentProperty extends AnnotationBasedPersistentProperty + implements ArangoPersistentProperty { + + private final FieldNamingStrategy fieldNamingStrategy; + + public DefaultArangoPersistentProperty(final java.lang.reflect.Field field, + final PropertyDescriptor propertyDescriptor, final PersistentEntity owner, + final SimpleTypeHolder simpleTypeHolder, final FieldNamingStrategy fieldNamingStrategy) { + super(field, propertyDescriptor, owner, simpleTypeHolder); + this.fieldNamingStrategy = fieldNamingStrategy != null ? fieldNamingStrategy + : PropertyNameFieldNamingStrategy.INSTANCE; + } + + @Override + protected Association createAssociation() { + return new Association<>(this, null); + } + + @Override + public boolean isKeyProperty() { + return findAnnotation(Key.class) != null; + } + + @Override + public boolean isRevProperty() { + return findAnnotation(Rev.class) != null; + } + + @Override + public Optional getRef() { + return Optional.ofNullable(findAnnotation(Ref.class)); + } + + @Override + public Optional getRelations() { + return Optional.ofNullable(findAnnotation(Relations.class)); + } + + @Override + public Optional getFrom() { + return Optional.ofNullable(findAnnotation(From.class)); + } + + @Override + public Optional getTo() { + return Optional.ofNullable(findAnnotation(To.class)); + } + + @Override + public String getFieldName() { + final String fieldName; + if (isIdProperty()) { + fieldName = "_id"; + } else if (isKeyProperty()) { + fieldName = "_key"; + } else if (isRevProperty()) { + fieldName = "_rev"; + } else if (getFrom().isPresent()) { + fieldName = "_from"; + } else if (getTo().isPresent()) { + fieldName = "_to"; + } else { + fieldName = getAnnotatedFieldName().orElse(fieldNamingStrategy.getFieldName(this)); + } + return fieldName; + } + + private Optional getAnnotatedFieldName() { + return Optional.ofNullable(findAnnotation(Field.class)) + .map(f -> StringUtils.hasText(f.value()) ? f.value() : null); + } + + @Override + public Optional getHashIndexed() { + return Optional.ofNullable(findAnnotation(HashIndexed.class)); + } + + @Override + public Optional getSkiplistIndexed() { + return Optional.ofNullable(findAnnotation(SkiplistIndexed.class)); + } + + @Override + public Optional getPersistentIndexed() { + return Optional.ofNullable(findAnnotation(PersistentIndexed.class)); + } + + @Override + public Optional getGeoIndexed() { + return Optional.ofNullable(findAnnotation(GeoIndexed.class)); + } + + @Override + public Optional getFulltextIndexed() { + return Optional.ofNullable(findAnnotation(FulltextIndexed.class)); + } + +} diff --git a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java index 40ecca760..15ec217e0 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoExtCursorIterator.java @@ -1,89 +1,90 @@ -/* - * 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.core.template; - -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; -import java.util.HashSet; -import java.util.Set; - -import org.springframework.data.mapping.model.SimpleTypeHolder; - -import com.arangodb.ArangoCursor; -import com.arangodb.entity.CursorEntity; -import com.arangodb.internal.ArangoCursorExecute; -import com.arangodb.internal.ArangoCursorIterator; -import com.arangodb.internal.InternalArangoDatabase; -import com.arangodb.springframework.core.convert.ArangoConverter; -import com.arangodb.springframework.core.convert.DBEntity; -import com.arangodb.velocypack.VPackSlice; - -/** - * @author Mark Vollmary - * @author Christian Lechner - * - * @param - * - */ -class ArangoExtCursorIterator extends ArangoCursorIterator { - - private static final SimpleTypeHolder ADDITIONAL_DESERIALIZABLE_TYPES; - - static { - final Set> simpleTypes = new HashSet<>(); - - // the following types apply only if the VPackJdk8Module is present on the - // ArangoDB Java driver, - // but there is no possibility to check - simpleTypes.add(Instant.class); - simpleTypes.add(LocalDate.class); - simpleTypes.add(LocalDateTime.class); - simpleTypes.add(OffsetDateTime.class); - simpleTypes.add(ZonedDateTime.class); - - ADDITIONAL_DESERIALIZABLE_TYPES = new SimpleTypeHolder(simpleTypes, false); - } - - private ArangoConverter converter; - - protected ArangoExtCursorIterator(final ArangoCursor cursor, final InternalArangoDatabase db, - final ArangoCursorExecute execute, final CursorEntity result) { - super(cursor, execute, db, result); - } - - public void setConverter(final ArangoConverter converter) { - this.converter = converter; - } - - @Override - protected R deserialize(final VPackSlice result, final Class type) { - return canDeserializeDirectly(type) ? super.deserialize(result, type) - : converter.read(type, super.deserialize(result, DBEntity.class)); - } - - private boolean canDeserializeDirectly(final Class type) { - return !converter.isEntityType(type) || ADDITIONAL_DESERIALIZABLE_TYPES.isSimpleType(type); - } - -} +/* + * 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.core.template; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; +import java.util.HashSet; +import java.util.Set; + +import org.springframework.data.mapping.model.SimpleTypeHolder; + +import com.arangodb.ArangoCursor; +import com.arangodb.entity.CursorEntity; +import com.arangodb.internal.ArangoCursorExecute; +import com.arangodb.internal.ArangoCursorIterator; +import com.arangodb.internal.InternalArangoDatabase; +import com.arangodb.springframework.core.convert.ArangoConverter; +import com.arangodb.springframework.core.convert.DBEntity; +import com.arangodb.velocypack.VPackSlice; + +/** + * @author Mark Vollmary + * @author Christian Lechner + * + * @param + * + */ +@SuppressWarnings("deprecation") +class ArangoExtCursorIterator extends ArangoCursorIterator { + + private static final SimpleTypeHolder ADDITIONAL_DESERIALIZABLE_TYPES; + + static { + final Set> simpleTypes = new HashSet<>(); + + // the following types apply only if the VPackJdk8Module is present on the + // ArangoDB Java driver, + // but there is no possibility to check + simpleTypes.add(Instant.class); + simpleTypes.add(LocalDate.class); + simpleTypes.add(LocalDateTime.class); + simpleTypes.add(OffsetDateTime.class); + simpleTypes.add(ZonedDateTime.class); + + ADDITIONAL_DESERIALIZABLE_TYPES = new SimpleTypeHolder(simpleTypes, false); + } + + private ArangoConverter converter; + + protected ArangoExtCursorIterator(final ArangoCursor cursor, final InternalArangoDatabase db, + final ArangoCursorExecute execute, final CursorEntity result) { + super(cursor, execute, db, result); + } + + public void setConverter(final ArangoConverter converter) { + this.converter = converter; + } + + @Override + protected R deserialize(final VPackSlice result, final Class type) { + return canDeserializeDirectly(type) ? super.deserialize(result, type) + : converter.read(type, super.deserialize(result, DBEntity.class)); + } + + private boolean canDeserializeDirectly(final Class type) { + return !converter.isEntityType(type) || ADDITIONAL_DESERIALIZABLE_TYPES.isSimpleType(type); + } + +} 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 2c83c36f1..49491d5b1 100644 --- a/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java +++ b/src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java @@ -81,6 +81,7 @@ * @author Christian Lechner * */ +@SuppressWarnings("deprecation") public class ArangoTemplate implements ArangoOperations, CollectionCallback { private volatile ArangoDBVersion version; @@ -100,10 +101,10 @@ public ArangoTemplate(final ArangoDB.Builder arango, final String database, fina } public ArangoTemplate(final ArangoDB.Builder arango, final String database, final ArangoConverter converter, - final PersistenceExceptionTranslator exceptionTranslator) { + final PersistenceExceptionTranslator exceptionTranslator) { super(); - this.arango = arango.build() - ._setCursorInitializer(new com.arangodb.springframework.core.template.ArangoCursorInitializer(converter)); + this.arango = arango.build()._setCursorInitializer( + new com.arangodb.springframework.core.template.ArangoCursorInitializer(converter)); this.databaseName = database; this.converter = converter; this.exceptionTranslator = exceptionTranslator; @@ -157,13 +158,16 @@ private ArangoCollection _collection(final Class entityClass) { } private ArangoCollection _collection(final Class entityClass, final String id) { - final ArangoPersistentEntity persistentEntity = converter.getMappingContext().getPersistentEntity(entityClass); + final ArangoPersistentEntity persistentEntity = converter.getMappingContext() + .getPersistentEntity(entityClass); final String name = determineCollectionFromId(Optional.ofNullable(id)).orElse(persistentEntity.getCollection()); return _collection(name, persistentEntity, persistentEntity.getCollectionOptions()); } - private ArangoCollection _collection(final String name, final ArangoPersistentEntity persistentEntity, - final CollectionCreateOptions options) { + private ArangoCollection _collection( + final String name, + final ArangoPersistentEntity persistentEntity, + final CollectionCreateOptions options) { return collectionCache.computeIfAbsent(name, collName -> { final ArangoCollection collection = db().collection(collName); @@ -187,8 +191,9 @@ private ArangoCollection _collection(final String name, final ArangoPersistentEn }); } - private static void ensureCollectionIndexes(final CollectionOperations collection, - final ArangoPersistentEntity persistentEntity) { + private static void ensureCollectionIndexes( + final CollectionOperations collection, + final ArangoPersistentEntity persistentEntity) { persistentEntity.getHashIndexes().stream().forEach(index -> ensureHashIndex(collection, index)); persistentEntity.getHashIndexedProperties().stream().forEach(p -> ensureHashIndex(collection, p)); persistentEntity.getSkiplistIndexes().stream().forEach(index -> ensureSkiplistIndex(collection, index)); @@ -202,13 +207,14 @@ private static void ensureCollectionIndexes(final CollectionOperations collectio } private static void ensureHashIndex(final CollectionOperations collection, final HashIndex annotation) { - collection.ensureHashIndex(Arrays.asList(annotation.fields()), new HashIndexOptions().unique(annotation.unique()) - .sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); + collection.ensureHashIndex(Arrays.asList(annotation.fields()), new HashIndexOptions() + .unique(annotation.unique()).sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); } private static void ensureHashIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { final HashIndexOptions options = new HashIndexOptions(); - value.getHashIndexed().ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); + value.getHashIndexed() + .ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); collection.ensureHashIndex(Collections.singleton(value.getFieldName()), options); } @@ -217,7 +223,9 @@ private static void ensureSkiplistIndex(final CollectionOperations collection, f .unique(annotation.unique()).sparse(annotation.sparse()).deduplicate(annotation.deduplicate())); } - private static void ensureSkiplistIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { + private static void ensureSkiplistIndex( + final CollectionOperations collection, + final ArangoPersistentProperty value) { final SkiplistIndexOptions options = new SkiplistIndexOptions(); value.getSkiplistIndexed() .ifPresent(i -> options.unique(i.unique()).sparse(i.sparse()).deduplicate(i.deduplicate())); @@ -226,18 +234,20 @@ private static void ensureSkiplistIndex(final CollectionOperations collection, f private static void ensurePersistentIndex(final CollectionOperations collection, final PersistentIndex annotation) { collection.ensurePersistentIndex(Arrays.asList(annotation.fields()), - new PersistentIndexOptions().unique(annotation.unique()).sparse(annotation.sparse())); + new PersistentIndexOptions().unique(annotation.unique()).sparse(annotation.sparse())); } - private static void ensurePersistentIndex(final CollectionOperations collection, - final ArangoPersistentProperty value) { + private static void ensurePersistentIndex( + final CollectionOperations collection, + final ArangoPersistentProperty value) { final PersistentIndexOptions options = new PersistentIndexOptions(); value.getPersistentIndexed().ifPresent(i -> options.unique(i.unique()).sparse(i.sparse())); collection.ensurePersistentIndex(Collections.singleton(value.getFieldName()), options); } private static void ensureGeoIndex(final CollectionOperations collection, final GeoIndex annotation) { - collection.ensureGeoIndex(Arrays.asList(annotation.fields()), new GeoIndexOptions().geoJson(annotation.geoJson())); + collection.ensureGeoIndex(Arrays.asList(annotation.fields()), + new GeoIndexOptions().geoJson(annotation.geoJson())); } private static void ensureGeoIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { @@ -248,10 +258,12 @@ private static void ensureGeoIndex(final CollectionOperations collection, final private static void ensureFulltextIndex(final CollectionOperations collection, final FulltextIndex annotation) { collection.ensureFulltextIndex(Collections.singleton(annotation.field()), - new FulltextIndexOptions().minLength(annotation.minLength() > -1 ? annotation.minLength() : null)); + new FulltextIndexOptions().minLength(annotation.minLength() > -1 ? annotation.minLength() : null)); } - private static void ensureFulltextIndex(final CollectionOperations collection, final ArangoPersistentProperty value) { + private static void ensureFulltextIndex( + final CollectionOperations collection, + 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); @@ -304,11 +316,14 @@ public ArangoCursor query(final String query, final AqlQueryOptions optio } @Override - public ArangoCursor query(final String query, final Map bindVars, - final AqlQueryOptions options, final Class entityClass) throws DataAccessException { + public ArangoCursor query( + final String query, + final Map bindVars, + final AqlQueryOptions options, + final Class entityClass) throws DataAccessException { return db().query(query, - bindVars == null ? null : DBDocumentEntity.class.cast(toDBEntity(prepareBindVars(bindVars))), options, - entityClass); + bindVars == null ? null : DBDocumentEntity.class.cast(toDBEntity(prepareBindVars(bindVars))), options, + entityClass); } private Map prepareBindVars(final Map bindVars) { @@ -321,19 +336,22 @@ private Map prepareBindVars(final Map bindVars) } @Override - public MultiDocumentEntity delete(final Iterable values, final Class entityClass, - final DocumentDeleteOptions options) throws DataAccessException { + public MultiDocumentEntity delete( + final Iterable values, + final Class entityClass, + final DocumentDeleteOptions options) throws DataAccessException { try { - return _collection(entityClass).deleteDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), entityClass, - options); + return _collection(entityClass).deleteDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), + entityClass, options); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); } } @Override - public MultiDocumentEntity delete(final Iterable values, final Class entityClass) - throws DataAccessException { + public MultiDocumentEntity delete( + final Iterable values, + final Class entityClass) throws DataAccessException { return delete(values, entityClass, new DocumentDeleteOptions()); } @@ -353,8 +371,10 @@ public DocumentEntity delete(final String id, final Class entityClass) throws } @Override - public MultiDocumentEntity update(final Iterable values, final Class entityClass, - final DocumentUpdateOptions options) throws DataAccessException { + public MultiDocumentEntity update( + final Iterable values, + final Class entityClass, + final DocumentUpdateOptions options) throws DataAccessException { try { final MultiDocumentEntity res = _collection(entityClass) .updateDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); @@ -366,8 +386,9 @@ public MultiDocumentEntity update(final Iterable MultiDocumentEntity update(final Iterable values, final Class entityClass) - throws DataAccessException { + public MultiDocumentEntity update( + final Iterable values, + final Class entityClass) throws DataAccessException { return update(values, entityClass, new DocumentUpdateOptions()); } @@ -376,7 +397,7 @@ public DocumentEntity update(final String id, final Object value, final Document throws DataAccessException { try { final DocumentEntity res = _collection(value.getClass(), id).updateDocument(determineDocumentKeyFromId(id), - toDBEntity(value), options); + toDBEntity(value), options); updateDBFields(value, res); return res; } catch (final ArangoDBException e) { @@ -390,8 +411,10 @@ public DocumentEntity update(final String id, final Object value) throws DataAcc } @Override - public MultiDocumentEntity replace(final Iterable values, final Class entityClass, - final DocumentReplaceOptions options) throws DataAccessException { + public MultiDocumentEntity replace( + final Iterable values, + final Class entityClass, + final DocumentReplaceOptions options) throws DataAccessException { try { final MultiDocumentEntity res = _collection(entityClass) .replaceDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); @@ -403,8 +426,9 @@ public MultiDocumentEntity replace(final Iterable< } @Override - public MultiDocumentEntity replace(final Iterable values, final Class entityClass) - throws DataAccessException { + public MultiDocumentEntity replace( + final Iterable values, + final Class entityClass) throws DataAccessException { return replace(values, entityClass, new DocumentReplaceOptions()); } @@ -413,7 +437,7 @@ public DocumentEntity replace(final String id, final Object value, final Documen throws DataAccessException { try { final DocumentEntity res = _collection(value.getClass(), id).replaceDocument(determineDocumentKeyFromId(id), - toDBEntity(value), options); + toDBEntity(value), options); updateDBFields(value, res); return res; } catch (final ArangoDBException e) { @@ -430,8 +454,8 @@ public DocumentEntity replace(final String id, final Object value) throws DataAc public Optional find(final String id, final Class entityClass, final DocumentReadOptions options) throws DataAccessException { try { - final DBEntity doc = _collection(entityClass, id).getDocument(determineDocumentKeyFromId(id), DBEntity.class, - options); + final DBEntity doc = _collection(entityClass, id).getDocument(determineDocumentKeyFromId(id), + DBEntity.class, options); return Optional.ofNullable(fromDBEntity(entityClass, doc)); } catch (final ArangoDBException e) { throw translateExceptionIfPossible(e); @@ -467,8 +491,10 @@ public Iterable find(final Iterable ids, final Class entityCla } @Override - public MultiDocumentEntity insert(final Iterable values, final Class entityClass, - final DocumentCreateOptions options) throws DataAccessException { + public MultiDocumentEntity insert( + final Iterable values, + final Class entityClass, + final DocumentCreateOptions options) throws DataAccessException { try { final MultiDocumentEntity res = _collection(entityClass) .insertDocuments(DBCollectionEntity.class.cast(toDBEntity(values)), options); @@ -480,8 +506,9 @@ public MultiDocumentEntity insert(final Iterable MultiDocumentEntity insert(final Iterable values, final Class entityClass) - throws DataAccessException { + public MultiDocumentEntity insert( + final Iterable values, + final Class entityClass) throws DataAccessException { return insert(values, entityClass, new DocumentCreateOptions()); } diff --git a/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java b/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java index 92afcf062..ec1bb30fc 100644 --- a/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java +++ b/src/test/java/com/arangodb/springframework/core/mapping/ArangoMappingTest.java @@ -66,6 +66,7 @@ * @author Christian Lechner * */ +@SuppressWarnings("deprecation") @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { ArangoTestConfiguration.class }) public class ArangoMappingTest extends AbstractArangoTest { @@ -129,9 +130,8 @@ public static class OnlyIdTestEntity { public void supplementKey() { final OnlyIdTestEntity value = new OnlyIdTestEntity(); template.insert(value); - final List result = template - .query("RETURN @doc", new MapBuilder().put("doc", value).get(), new AqlQueryOptions(), BasicTestEntity.class) - .asListRemaining(); + final List result = template.query("RETURN @doc", new MapBuilder().put("doc", value).get(), + new AqlQueryOptions(), BasicTestEntity.class).asListRemaining(); assertThat(result.size(), is(1)); assertThat(result.get(0).getId(), is(value.id)); assertThat(result.get(0).getKey(), is(value.id.split("/")[1])); @@ -149,7 +149,7 @@ public void fieldNameAnnotation() { entity.test = "1234"; final DocumentEntity res = template.insert(entity); final VPackSlice slice = template.driver().db(ArangoTestConfiguration.DB).getDocument(res.getId(), - VPackSlice.class); + VPackSlice.class); assertThat(slice, is(notNullValue())); assertThat(slice.get("alt-test").isString(), is(true)); assertThat(slice.get("alt-test").getAsString(), is(entity.test)); @@ -192,10 +192,10 @@ public static class MultipleNestedDocumentTestEntity extends BasicTestEntity { public void multipleNestedDocuments() { final MultipleNestedDocumentTestEntity entity = new MultipleNestedDocumentTestEntity(); entity.entities = new ArrayList<>(Arrays.asList(new NestedDocumentTestEntity("0"), - new NestedDocumentTestEntity("1"), new NestedDocumentTestEntity("2"))); + new NestedDocumentTestEntity("1"), new NestedDocumentTestEntity("2"))); template.insert(entity); - final MultipleNestedDocumentTestEntity document = template.find(entity.id, MultipleNestedDocumentTestEntity.class) - .get(); + final MultipleNestedDocumentTestEntity document = template + .find(entity.id, MultipleNestedDocumentTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.entities, is(notNullValue())); assertThat(document.entities.size(), is(3)); @@ -210,10 +210,10 @@ public static class MultipleNestedCollectionsTestEntity extends BasicTestEntity public void multipleNestedCollections() { final MultipleNestedCollectionsTestEntity entity = new MultipleNestedCollectionsTestEntity(); entity.entities = new ArrayList<>(Arrays.asList( - new ArrayList<>(Arrays.asList(new NestedDocumentTestEntity("00"), new NestedDocumentTestEntity("01"), - new NestedDocumentTestEntity("02"))), - new ArrayList<>(Arrays.asList(new NestedDocumentTestEntity("10"), new NestedDocumentTestEntity("11"), - new NestedDocumentTestEntity("12"))))); + new ArrayList<>(Arrays.asList(new NestedDocumentTestEntity("00"), new NestedDocumentTestEntity("01"), + new NestedDocumentTestEntity("02"))), + new ArrayList<>(Arrays.asList(new NestedDocumentTestEntity("10"), new NestedDocumentTestEntity("11"), + new NestedDocumentTestEntity("12"))))); template.insert(entity); final MultipleNestedCollectionsTestEntity document = template .find(entity.id, MultipleNestedCollectionsTestEntity.class).get(); @@ -245,7 +245,8 @@ public void singleNestedMap() { assertThat(document, is(notNullValue())); assertThat(document.entities, is(notNullValue())); final Map collect = document.entities.entrySet().stream() - .map(e -> new String[] { e.getKey(), e.getValue().test }).collect(Collectors.toMap(k -> k[0], v -> v[1])); + .map(e -> new String[] { e.getKey(), e.getValue().test }) + .collect(Collectors.toMap(k -> k[0], v -> v[1])); for (int i = 0; i <= 2; i++) { assertThat(collect.get(String.valueOf(i)), is(String.valueOf(i))); } @@ -678,8 +679,8 @@ public ConstructorWithParamTestEntity(final String value) { public void constructorWithParam() { final ConstructorWithParamTestEntity entity = new ConstructorWithParamTestEntity("test"); template.insert(entity); - final ConstructorWithParamTestEntity document = template.find(entity.getId(), ConstructorWithParamTestEntity.class) - .get(); + final ConstructorWithParamTestEntity document = template + .find(entity.getId(), ConstructorWithParamTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(entity.value)); } @@ -693,7 +694,7 @@ public static class ConstructorWithMultipleParamsTestEntity extends BasicTestEnt private final String[] value6; public ConstructorWithMultipleParamsTestEntity(final String value1, final boolean value2, final double value3, - final long value4, final int value5, final String[] value6) { + final long value4, final int value5, final String[] value6) { super(); this.value1 = value1; this.value2 = value2; @@ -727,7 +728,8 @@ public static class ConstructorWithRefParamsTestEntity extends BasicTestEntity { @Ref private final Collection value2; - public ConstructorWithRefParamsTestEntity(final BasicTestEntity value1, final Collection value2) { + public ConstructorWithRefParamsTestEntity(final BasicTestEntity value1, + final Collection value2) { super(); this.value1 = value1; this.value2 = value2; @@ -750,7 +752,8 @@ public void constructorWithRefParams() { assertThat(document, is(notNullValue())); assertThat(document.value1.id, is(value1.id)); assertThat(document.value2.size(), is(2)); - assertThat(document.value2.stream().map((e) -> e.id).collect(Collectors.toList()), hasItems(value2.id, value3.id)); + assertThat(document.value2.stream().map((e) -> e.id).collect(Collectors.toList()), + hasItems(value2.id, value3.id)); } public static class ConstructorWithRefLazyParamsTestEntity extends BasicTestEntity { @@ -760,7 +763,7 @@ public static class ConstructorWithRefLazyParamsTestEntity extends BasicTestEnti private final Collection value2; public ConstructorWithRefLazyParamsTestEntity(final BasicTestEntity value1, - final Collection value2) { + final Collection value2) { super(); this.value1 = value1; this.value2 = value2; @@ -784,7 +787,7 @@ public void constructorWithRefLazyParams() { assertThat(document.value1.getId(), is(value1.id)); assertThat(document.value2.size(), is(2)); assertThat(document.value2.stream().map((e) -> e.getId()).collect(Collectors.toList()), - hasItems(value2.id, value3.id)); + hasItems(value2.id, value3.id)); } public static class ConstructorWithRelationsParamsTestEntity extends BasicTestEntity { @@ -811,7 +814,8 @@ public void constructorWithRelationsParams() { final ConstructorWithRelationsParamsTestEntity document = template .find(entity.id, ConstructorWithRelationsParamsTestEntity.class).get(); assertThat(document, is(notNullValue())); - assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), hasItems(vertex1.id, vertex2.id)); + assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), + hasItems(vertex1.id, vertex2.id)); } public static class ConstructorWithRelationsLazyParamsTestEntity extends BasicTestEntity { @@ -838,7 +842,8 @@ public void constructorWithRelationsLazyParams() { final ConstructorWithRelationsLazyParamsTestEntity document = template .find(entity.id, ConstructorWithRelationsLazyParamsTestEntity.class).get(); assertThat(document, is(notNullValue())); - assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), hasItems(vertex1.id, vertex2.id)); + assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), + hasItems(vertex1.id, vertex2.id)); } public static class ConstructorWithFromParamsTestEntity extends BasicTestEntity { @@ -891,7 +896,7 @@ public void constructorWithFromLazyParams() { .find(entity.id, ConstructorWithFromLazyParamsTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value.stream().map((e) -> e.getId()).collect(Collectors.toList()), - hasItems(edge1.id, edge2.id)); + hasItems(edge1.id, edge2.id)); } public static class ConstructorWithToParamsTestEntity extends BasicTestEntity { @@ -914,8 +919,8 @@ public void constructorWithToParams() { final BasicEdgeLazyTestEntity edge2 = new BasicEdgeLazyTestEntity(from, entity); template.insert(edge1); template.insert(edge2); - final ConstructorWithToParamsTestEntity document = template.find(entity.id, ConstructorWithToParamsTestEntity.class) - .get(); + final ConstructorWithToParamsTestEntity document = template + .find(entity.id, ConstructorWithToParamsTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value.stream().map((e) -> e.id).collect(Collectors.toList()), hasItems(edge1.id, edge2.id)); } @@ -944,7 +949,7 @@ public void constructorWithToLazyParams() { .find(entity.id, ConstructorWithToLazyParamsTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value.stream().map((e) -> e.getId()).collect(Collectors.toList()), - hasItems(edge1.id, edge2.id)); + hasItems(edge1.id, edge2.id)); } public static class EdgeConstructorWithFromToParamsTestEntity extends BasicEdgeTestEntity { @@ -994,8 +999,8 @@ public void edgeConstructorWithFromToLazyParams() { final BasicTestEntity to = new BasicTestEntity(); template.insert(from); template.insert(to); - final EdgeConstructorWithFromToLazyParamsTestEntity edge = new EdgeConstructorWithFromToLazyParamsTestEntity(from, - to); + final EdgeConstructorWithFromToLazyParamsTestEntity edge = new EdgeConstructorWithFromToLazyParamsTestEntity( + from, to); template.insert(edge); final EdgeConstructorWithFromToLazyParamsTestEntity document = template .find(edge.id, EdgeConstructorWithFromToLazyParamsTestEntity.class).get(); @@ -1066,8 +1071,8 @@ public void simplePropertyInheritanceMapping() { final PropertyInheritanceTestEntity entity = new PropertyInheritanceTestEntity(); entity.value = child; template.insert(entity); - final PropertyInheritanceTestEntity document = template.find(entity.getId(), PropertyInheritanceTestEntity.class) - .get(); + final PropertyInheritanceTestEntity document = template + .find(entity.getId(), PropertyInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(SimpleBasicChildTestEntity.class))); assertThat(((SimpleBasicChildTestEntity) document.value).field, is(child.field)); @@ -1082,8 +1087,8 @@ public void complexPropertyInheritanceMapping() { final PropertyInheritanceTestEntity entity = new PropertyInheritanceTestEntity(); entity.value = child; template.insert(entity); - final PropertyInheritanceTestEntity document = template.find(entity.getId(), PropertyInheritanceTestEntity.class) - .get(); + final PropertyInheritanceTestEntity document = template + .find(entity.getId(), PropertyInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(ComplexBasicChildTestEntity.class))); final ComplexBasicChildTestEntity complexDocument = (ComplexBasicChildTestEntity) document.value; @@ -1274,7 +1279,8 @@ public void constructorPropertyInheritanceMapping() { innerChild.field = "value"; final ComplexBasicChildTestEntity child = new ComplexBasicChildTestEntity(); child.nestedEntity = innerChild; - final ConstructorWithPropertyInheritanceTestEntity entity = new ConstructorWithPropertyInheritanceTestEntity(child); + final ConstructorWithPropertyInheritanceTestEntity entity = new ConstructorWithPropertyInheritanceTestEntity( + child); template.insert(entity); final ConstructorWithPropertyInheritanceTestEntity document = template .find(entity.getId(), ConstructorWithPropertyInheritanceTestEntity.class).get(); @@ -1308,8 +1314,8 @@ public void listInMapInheritanceMapping() { final ListInMapInheritanceTestEntity entity = new ListInMapInheritanceTestEntity(); entity.value = map; template.insert(entity); - final ListInMapInheritanceTestEntity document = template.find(entity.getId(), ListInMapInheritanceTestEntity.class) - .get(); + final ListInMapInheritanceTestEntity document = template + .find(entity.getId(), ListInMapInheritanceTestEntity.class).get(); assertThat(document, is(notNullValue())); assertThat(document.value, is(instanceOf(Map.class))); for (final Map.Entry> entry : document.value.entrySet()) { diff --git a/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java b/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java index 559ea2427..397210ec7 100644 --- a/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java +++ b/src/test/java/com/arangodb/springframework/core/template/ArangoTemplateTest.java @@ -1,409 +1,410 @@ -/* - * 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.core.template; - -import static org.hamcrest.Matchers.hasItems; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertThat; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.Transient; -import org.springframework.data.domain.Persistable; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import com.arangodb.ArangoCursor; -import com.arangodb.entity.ArangoDBVersion; -import com.arangodb.entity.BaseDocument; -import com.arangodb.entity.DocumentEntity; -import com.arangodb.entity.MultiDocumentEntity; -import com.arangodb.model.AqlQueryOptions; -import com.arangodb.springframework.AbstractArangoTest; -import com.arangodb.springframework.ArangoTestConfiguration; -import com.arangodb.springframework.annotation.Key; -import com.arangodb.springframework.core.ArangoOperations.UpsertStrategy; -import com.arangodb.springframework.testdata.Address; -import com.arangodb.springframework.testdata.Customer; -import com.arangodb.springframework.testdata.Product; -import com.arangodb.util.MapBuilder; -import com.arangodb.velocypack.VPackSlice; - -/** - * @author Mark Vollmary - * - */ -@RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes = { ArangoTestConfiguration.class }) -public class ArangoTemplateTest extends AbstractArangoTest { - - @Test - public void template() { - final ArangoDBVersion version = template.getVersion(); - assertThat(version, is(notNullValue())); - assertThat(version.getLicense(), is(notNullValue())); - assertThat(version.getServer(), is(notNullValue())); - assertThat(version.getVersion(), is(notNullValue())); - } - - @Test - public void insertDocument() { - final DocumentEntity res = template.insert(new Customer("John", "Doe", 30)); - assertThat(res, is(notNullValue())); - assertThat(res.getId(), is(notNullValue())); - } - - @Test - public void insertDocuments() { - final Customer c1 = new Customer(); - final Customer c2 = new Customer(); - final Customer c3 = new Customer(); - c3.setKey("3"); - final Customer c4 = new Customer(); - c4.setKey("3"); - final MultiDocumentEntity res = template.insert(Arrays.asList(c1, c2, c3, c4), - Customer.class); - assertThat(res, is(notNullValue())); - assertThat(res.getDocuments().size(), is(3)); - assertThat(res.getErrors().size(), is(1)); - assertThat(c1.getId(), is(notNullValue())); - assertThat(c2.getId(), is(notNullValue())); - assertThat(c3.getId(), is(notNullValue())); - } - - @Test - public void insertDocumentWithCollName() { - final DocumentEntity res = template.insert("customer", new Customer("John", "Doe", 30)); - assertThat(res, is(notNullValue())); - assertThat(res.getId(), is(notNullValue())); - } - - @Test - public void upsertReplace() { - final Customer customer = new Customer("John", "Doe", 30); - template.upsert(customer, UpsertStrategy.REPLACE); - assertThat(template.find(customer.getId(), Customer.class).get().getAge(), is(30)); - customer.setAge(35); - template.upsert(customer, UpsertStrategy.REPLACE); - assertThat(template.find(customer.getId(), Customer.class).get().getAge(), is(35)); - } - - @Test - public void upsertUpdate() { - final Customer customer = new Customer("John", "Doe", 30); - template.upsert(customer, UpsertStrategy.UPDATE); - assertThat(template.find(customer.getId(), Customer.class).get().getAge(), is(30)); - customer.setAge(35); - template.upsert(customer, UpsertStrategy.UPDATE); - assertThat(template.find(customer.getId(), Customer.class).get().getAge(), is(35)); - } - - @Test - public void upsertReplaceMultiple() { - final Customer c1 = new Customer("John", "Doe", 30); - final Customer c2 = new Customer("John2", "Doe2", 30); - template.upsert(Arrays.asList(c1, c2), UpsertStrategy.REPLACE); - assertThat(template.find(c1.getId(), Customer.class).get().getAge(), is(30)); - assertThat(template.find(c2.getId(), Customer.class).get().getAge(), is(30)); - c1.setAge(35); - c2.setAge(35); - final Customer c3 = new Customer("John3", "Doe2", 30); - template.upsert(Arrays.asList(c1, c2, c3), UpsertStrategy.REPLACE); - assertThat(template.find(c1.getId(), Customer.class).get().getAge(), is(35)); - assertThat(template.find(c2.getId(), Customer.class).get().getAge(), is(35)); - assertThat(template.find(c3.getId(), Customer.class).get().getAge(), is(30)); - } - - @Test - public void upsertUpdateMultiple() { - final Customer c1 = new Customer("John", "Doe", 30); - final Customer c2 = new Customer("John2", "Doe2", 30); - template.upsert(Arrays.asList(c1, c2), UpsertStrategy.UPDATE); - assertThat(template.find(c1.getId(), Customer.class).get().getAge(), is(30)); - assertThat(template.find(c2.getId(), Customer.class).get().getAge(), is(30)); - c1.setAge(35); - c2.setAge(35); - final Customer c3 = new Customer("John3", "Doe2", 30); - template.upsert(Arrays.asList(c1, c2, c3), UpsertStrategy.UPDATE); - assertThat(template.find(c1.getId(), Customer.class).get().getAge(), is(35)); - assertThat(template.find(c2.getId(), Customer.class).get().getAge(), is(35)); - assertThat(template.find(c3.getId(), Customer.class).get().getAge(), is(30)); - } - - @Test - public void getDocument() { - final DocumentEntity res = template.insert(new Customer("John", "Doe", 30, new Address("22162–1010"))); - final Customer customer = template.find(res.getId(), Customer.class).get(); - assertThat(customer, is(notNullValue())); - assertThat(customer.getName(), is("John")); - assertThat(customer.getSurname(), is("Doe")); - assertThat(customer.getAge(), is(30)); - assertThat(customer.getAddress(), is(notNullValue())); - assertThat(customer.getAddress().getZipCode(), is("22162–1010")); - } - - @Test - public void getDocuments() { - final Customer c1 = new Customer("John", "Doe", 30); - final Customer c2 = new Customer("John2", "Doe", 30); - template.insert(Arrays.asList(c1, c2), Customer.class); - final Iterable customers = template.find(Arrays.asList(c1.getId(), c2.getId()), Customer.class); - assertThat(customers, is(notNullValue())); - assertThat( - StreamSupport.stream(customers.spliterator(), false).map((e) -> e.getId()).collect(Collectors.toList()), - hasItems(c1.getId(), c2.getId())); - } - - @Test - public void getAllDocuments() { - final Customer c1 = new Customer("John", "Doe", 30); - final Customer c2 = new Customer("John2", "Doe", 30); - template.insert(Arrays.asList(c1, c2), Customer.class); - final Iterable customers = template.findAll(Customer.class); - assertThat(customers, is(notNullValue())); - assertThat( - StreamSupport.stream(customers.spliterator(), false).map((e) -> e.getId()).collect(Collectors.toList()), - hasItems(c1.getId(), c2.getId())); - } - - @Test - public void replaceDocument() { - final DocumentEntity res = template.insert(new Customer("John", "Doe", 30)); - final DocumentEntity replaceDocument = template.replace(res.getId(), new Customer("Jane", "Doe", 26)); - assertThat(replaceDocument, is(notNullValue())); - final Customer customer = template.find(res.getId(), Customer.class).get(); - assertThat(customer, is(notNullValue())); - assertThat(customer.getName(), is("Jane")); - assertThat(customer.getSurname(), is("Doe")); - assertThat(customer.getAge(), is(26)); - } - - @Test - public void replaceDocuments() { - final DocumentEntity a = template.insert(new Product("a")); - final DocumentEntity b = template.insert(new Product("b")); - - final Product documentA = template.find(a.getId(), Product.class).get(); - documentA.setName("aa"); - final Product documentB = template.find(b.getId(), Product.class).get(); - documentB.setName("bb"); - - final MultiDocumentEntity res = template.replace(Arrays.asList(documentA, documentB), - Product.class); - assertThat(res, is(notNullValue())); - assertThat(res.getDocuments().size(), is(2)); - - final Product newA = template.find(a.getId(), Product.class).get(); - assertThat(newA.getName(), is("aa")); - final Product newB = template.find(b.getId(), Product.class).get(); - assertThat(newB.getName(), is("bb")); - } - - @Test - public void updateDocument() { - final DocumentEntity res = template.insert(new Customer("John", "Doe", 30)); - template.update(res.getId(), new Customer("Jane", "Doe", 26)); - final Customer customer = template.find(res.getId(), Customer.class).get(); - assertThat(customer, is(notNullValue())); - assertThat(customer.getName(), is("Jane")); - assertThat(customer.getSurname(), is("Doe")); - assertThat(customer.getAge(), is(26)); - } - - @Test - public void updateDocuments() { - final DocumentEntity a = template.insert(new Product("a")); - final DocumentEntity b = template.insert(new Product("b")); - - final Product documentA = template.find(a.getId(), Product.class).get(); - documentA.setName("aa"); - final Product documentB = template.find(b.getId(), Product.class).get(); - documentB.setName("bb"); - - final MultiDocumentEntity res = template.update(Arrays.asList(documentA, documentB), - Product.class); - assertThat(res, is(notNullValue())); - assertThat(res.getDocuments().size(), is(2)); - - final Product newA = template.find(a.getId(), Product.class).get(); - assertThat(newA.getName(), is("aa")); - final Product newB = template.find(b.getId(), Product.class).get(); - assertThat(newB.getName(), is("bb")); - } - - @Test - public void deleteDocument() { - final DocumentEntity res = template.insert(new Customer("John", "Doe", 30)); - template.delete(res.getId(), Customer.class); - final Optional customer = template.find(res.getId(), Customer.class); - assertThat(customer.isPresent(), is(false)); - } - - @Test - public void deleteDocuments() { - final DocumentEntity a = template.insert(new Product("a")); - final DocumentEntity b = template.insert(new Product("b")); - - final Product documentA = template.find(a.getId(), Product.class).get(); - final Product documentB = template.find(b.getId(), Product.class).get(); - - final MultiDocumentEntity res = template.delete(Arrays.asList(documentA, documentB), - Product.class); - assertThat(res, is(notNullValue())); - assertThat(res.getDocuments().size(), is(2)); - - final Optional deletedA = template.find(a.getId(), Product.class); - assertThat(deletedA.isPresent(), is(false)); - final Optional deletedB = template.find(b.getId(), Product.class); - assertThat(deletedB.isPresent(), is(false)); - } - - @Test - public void query() { - template.insert(new Customer("John", "Doe", 30)); - final ArangoCursor cursor = template.query("FOR c IN @@coll FILTER c.name == @name RETURN c", - new MapBuilder().put("@coll", "customer").put("name", "John").get(), new AqlQueryOptions(), Customer.class); - assertThat(cursor, is(notNullValue())); - final List customers = cursor.asListRemaining(); - assertThat(customers.size(), is(1)); - assertThat(customers.get(0).getName(), is("John")); - assertThat(customers.get(0).getSurname(), is("Doe")); - assertThat(customers.get(0).getAge(), is(30)); - } - - @Test - public void queryWithoutBindParams() { - template.insert(new Customer("John", "Doe", 30)); - final ArangoCursor cursor = template.query("FOR c IN customer FILTER c.name == 'John' RETURN c", null, - new AqlQueryOptions(), Customer.class); - assertThat(cursor, is(notNullValue())); - final List customers = cursor.asListRemaining(); - assertThat(customers.size(), is(1)); - assertThat(customers.get(0).getName(), is("John")); - assertThat(customers.get(0).getSurname(), is("Doe")); - assertThat(customers.get(0).getAge(), is(30)); - } - - @SuppressWarnings("rawtypes") - @Test - public void queryMap() { - template.insert(new Customer("John", "Doe", 30)); - final ArangoCursor cursor = template.query("FOR c IN @@coll FILTER c.name == @name RETURN c", - new MapBuilder().put("@coll", "customer").put("name", "John").get(), new AqlQueryOptions(), Map.class); - assertThat(cursor, is(notNullValue())); - final List customers = cursor.asListRemaining(); - assertThat(customers.size(), is(1)); - assertThat(customers.get(0).get("name"), is("John")); - assertThat(customers.get(0).get("surname"), is("Doe")); - assertThat(customers.get(0).get("age"), is(30L)); - } - - @Test - public void queryBaseDocument() { - template.insert(new Customer("John", "Doe", 30)); - final ArangoCursor cursor = template.query("FOR c IN @@coll FILTER c.name == @name RETURN c", - new MapBuilder().put("@coll", "customer").put("name", "John").get(), new AqlQueryOptions(), - BaseDocument.class); - assertThat(cursor, is(notNullValue())); - final List customers = cursor.asListRemaining(); - assertThat(customers.size(), is(1)); - assertThat(customers.get(0).getAttribute("name"), is("John")); - assertThat(customers.get(0).getAttribute("surname"), is("Doe")); - assertThat(customers.get(0).getAttribute("age"), is(30L)); - } - - @Test - public void queryVPackSlice() { - template.insert(new Customer("John", "Doe", 30)); - final ArangoCursor cursor = template.query("FOR c IN @@coll FILTER c.name == @name RETURN c", - new MapBuilder().put("@coll", "customer").put("name", "John").get(), new AqlQueryOptions(), - VPackSlice.class); - assertThat(cursor, is(notNullValue())); - final List customers = cursor.asListRemaining(); - assertThat(customers.size(), is(1)); - assertThat(customers.get(0).get("name").getAsString(), is("John")); - assertThat(customers.get(0).get("surname").getAsString(), is("Doe")); - assertThat(customers.get(0).get("age").getAsInt(), is(30)); - } - - public static class NewEntityTest implements Persistable { - - @Id - private String id; - @Key - private final String key; - @Transient - private transient boolean persisted; - - public NewEntityTest(final String key) { - super(); - this.key = key; - } - - public void setPersisted(final boolean persisted) { - this.persisted = persisted; - } - - @Override - public boolean isNew() { - return !persisted; - } - - @Override - public String getId() { - return key; - } - } - - @SuppressWarnings("deprecation") - @Test - public void upsertWithUserGeneratedKey() { - final NewEntityTest entity = new NewEntityTest("test"); - template.upsert(entity, UpsertStrategy.REPLACE); - assertThat(template.collection(NewEntityTest.class).count(), is(1L)); - entity.setPersisted(true); - template.upsert(entity, UpsertStrategy.REPLACE); - assertThat(template.collection(NewEntityTest.class).count(), is(1L)); - } - - @SuppressWarnings("deprecation") - @Test - public void mutliUpsertWithUserGeneratedKey() { - final NewEntityTest entity1 = new NewEntityTest("test1"); - final NewEntityTest entity2 = new NewEntityTest("test2"); - template.upsert(Arrays.asList(entity1, entity2), UpsertStrategy.REPLACE); - assertThat(template.collection(NewEntityTest.class).count(), is(2L)); - entity1.setPersisted(true); - entity2.setPersisted(true); - template.upsert(Arrays.asList(entity1, entity2), UpsertStrategy.REPLACE); - assertThat(template.collection(NewEntityTest.class).count(), is(2L)); - } - -} +/* + * 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.core.template; + +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Transient; +import org.springframework.data.domain.Persistable; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.arangodb.ArangoCursor; +import com.arangodb.entity.ArangoDBVersion; +import com.arangodb.entity.BaseDocument; +import com.arangodb.entity.DocumentEntity; +import com.arangodb.entity.MultiDocumentEntity; +import com.arangodb.model.AqlQueryOptions; +import com.arangodb.springframework.AbstractArangoTest; +import com.arangodb.springframework.ArangoTestConfiguration; +import com.arangodb.springframework.annotation.Key; +import com.arangodb.springframework.core.ArangoOperations.UpsertStrategy; +import com.arangodb.springframework.testdata.Address; +import com.arangodb.springframework.testdata.Customer; +import com.arangodb.springframework.testdata.Product; +import com.arangodb.util.MapBuilder; +import com.arangodb.velocypack.VPackSlice; + +/** + * @author Mark Vollmary + * + */ +@SuppressWarnings("deprecation") +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = { ArangoTestConfiguration.class }) +public class ArangoTemplateTest extends AbstractArangoTest { + + @Test + public void template() { + final ArangoDBVersion version = template.getVersion(); + assertThat(version, is(notNullValue())); + assertThat(version.getLicense(), is(notNullValue())); + assertThat(version.getServer(), is(notNullValue())); + assertThat(version.getVersion(), is(notNullValue())); + } + + @Test + public void insertDocument() { + final DocumentEntity res = template.insert(new Customer("John", "Doe", 30)); + assertThat(res, is(notNullValue())); + assertThat(res.getId(), is(notNullValue())); + } + + @Test + public void insertDocuments() { + final Customer c1 = new Customer(); + final Customer c2 = new Customer(); + final Customer c3 = new Customer(); + c3.setKey("3"); + final Customer c4 = new Customer(); + c4.setKey("3"); + final MultiDocumentEntity res = template.insert(Arrays.asList(c1, c2, c3, c4), + Customer.class); + assertThat(res, is(notNullValue())); + assertThat(res.getDocuments().size(), is(3)); + assertThat(res.getErrors().size(), is(1)); + assertThat(c1.getId(), is(notNullValue())); + assertThat(c2.getId(), is(notNullValue())); + assertThat(c3.getId(), is(notNullValue())); + } + + @Test + public void insertDocumentWithCollName() { + final DocumentEntity res = template.insert("customer", new Customer("John", "Doe", 30)); + assertThat(res, is(notNullValue())); + assertThat(res.getId(), is(notNullValue())); + } + + @Test + public void upsertReplace() { + final Customer customer = new Customer("John", "Doe", 30); + template.upsert(customer, UpsertStrategy.REPLACE); + assertThat(template.find(customer.getId(), Customer.class).get().getAge(), is(30)); + customer.setAge(35); + template.upsert(customer, UpsertStrategy.REPLACE); + assertThat(template.find(customer.getId(), Customer.class).get().getAge(), is(35)); + } + + @Test + public void upsertUpdate() { + final Customer customer = new Customer("John", "Doe", 30); + template.upsert(customer, UpsertStrategy.UPDATE); + assertThat(template.find(customer.getId(), Customer.class).get().getAge(), is(30)); + customer.setAge(35); + template.upsert(customer, UpsertStrategy.UPDATE); + assertThat(template.find(customer.getId(), Customer.class).get().getAge(), is(35)); + } + + @Test + public void upsertReplaceMultiple() { + final Customer c1 = new Customer("John", "Doe", 30); + final Customer c2 = new Customer("John2", "Doe2", 30); + template.upsert(Arrays.asList(c1, c2), UpsertStrategy.REPLACE); + assertThat(template.find(c1.getId(), Customer.class).get().getAge(), is(30)); + assertThat(template.find(c2.getId(), Customer.class).get().getAge(), is(30)); + c1.setAge(35); + c2.setAge(35); + final Customer c3 = new Customer("John3", "Doe2", 30); + template.upsert(Arrays.asList(c1, c2, c3), UpsertStrategy.REPLACE); + assertThat(template.find(c1.getId(), Customer.class).get().getAge(), is(35)); + assertThat(template.find(c2.getId(), Customer.class).get().getAge(), is(35)); + assertThat(template.find(c3.getId(), Customer.class).get().getAge(), is(30)); + } + + @Test + public void upsertUpdateMultiple() { + final Customer c1 = new Customer("John", "Doe", 30); + final Customer c2 = new Customer("John2", "Doe2", 30); + template.upsert(Arrays.asList(c1, c2), UpsertStrategy.UPDATE); + assertThat(template.find(c1.getId(), Customer.class).get().getAge(), is(30)); + assertThat(template.find(c2.getId(), Customer.class).get().getAge(), is(30)); + c1.setAge(35); + c2.setAge(35); + final Customer c3 = new Customer("John3", "Doe2", 30); + template.upsert(Arrays.asList(c1, c2, c3), UpsertStrategy.UPDATE); + assertThat(template.find(c1.getId(), Customer.class).get().getAge(), is(35)); + assertThat(template.find(c2.getId(), Customer.class).get().getAge(), is(35)); + assertThat(template.find(c3.getId(), Customer.class).get().getAge(), is(30)); + } + + @Test + public void getDocument() { + final DocumentEntity res = template.insert(new Customer("John", "Doe", 30, new Address("22162–1010"))); + final Customer customer = template.find(res.getId(), Customer.class).get(); + assertThat(customer, is(notNullValue())); + assertThat(customer.getName(), is("John")); + assertThat(customer.getSurname(), is("Doe")); + assertThat(customer.getAge(), is(30)); + assertThat(customer.getAddress(), is(notNullValue())); + assertThat(customer.getAddress().getZipCode(), is("22162–1010")); + } + + @Test + public void getDocuments() { + final Customer c1 = new Customer("John", "Doe", 30); + final Customer c2 = new Customer("John2", "Doe", 30); + template.insert(Arrays.asList(c1, c2), Customer.class); + final Iterable customers = template.find(Arrays.asList(c1.getId(), c2.getId()), Customer.class); + assertThat(customers, is(notNullValue())); + assertThat( + StreamSupport.stream(customers.spliterator(), false).map((e) -> e.getId()).collect(Collectors.toList()), + hasItems(c1.getId(), c2.getId())); + } + + @Test + public void getAllDocuments() { + final Customer c1 = new Customer("John", "Doe", 30); + final Customer c2 = new Customer("John2", "Doe", 30); + template.insert(Arrays.asList(c1, c2), Customer.class); + final Iterable customers = template.findAll(Customer.class); + assertThat(customers, is(notNullValue())); + assertThat( + StreamSupport.stream(customers.spliterator(), false).map((e) -> e.getId()).collect(Collectors.toList()), + hasItems(c1.getId(), c2.getId())); + } + + @Test + public void replaceDocument() { + final DocumentEntity res = template.insert(new Customer("John", "Doe", 30)); + final DocumentEntity replaceDocument = template.replace(res.getId(), new Customer("Jane", "Doe", 26)); + assertThat(replaceDocument, is(notNullValue())); + final Customer customer = template.find(res.getId(), Customer.class).get(); + assertThat(customer, is(notNullValue())); + assertThat(customer.getName(), is("Jane")); + assertThat(customer.getSurname(), is("Doe")); + assertThat(customer.getAge(), is(26)); + } + + @Test + public void replaceDocuments() { + final DocumentEntity a = template.insert(new Product("a")); + final DocumentEntity b = template.insert(new Product("b")); + + final Product documentA = template.find(a.getId(), Product.class).get(); + documentA.setName("aa"); + final Product documentB = template.find(b.getId(), Product.class).get(); + documentB.setName("bb"); + + final MultiDocumentEntity res = template.replace(Arrays.asList(documentA, documentB), + Product.class); + assertThat(res, is(notNullValue())); + assertThat(res.getDocuments().size(), is(2)); + + final Product newA = template.find(a.getId(), Product.class).get(); + assertThat(newA.getName(), is("aa")); + final Product newB = template.find(b.getId(), Product.class).get(); + assertThat(newB.getName(), is("bb")); + } + + @Test + public void updateDocument() { + final DocumentEntity res = template.insert(new Customer("John", "Doe", 30)); + template.update(res.getId(), new Customer("Jane", "Doe", 26)); + final Customer customer = template.find(res.getId(), Customer.class).get(); + assertThat(customer, is(notNullValue())); + assertThat(customer.getName(), is("Jane")); + assertThat(customer.getSurname(), is("Doe")); + assertThat(customer.getAge(), is(26)); + } + + @Test + public void updateDocuments() { + final DocumentEntity a = template.insert(new Product("a")); + final DocumentEntity b = template.insert(new Product("b")); + + final Product documentA = template.find(a.getId(), Product.class).get(); + documentA.setName("aa"); + final Product documentB = template.find(b.getId(), Product.class).get(); + documentB.setName("bb"); + + final MultiDocumentEntity res = template.update(Arrays.asList(documentA, documentB), + Product.class); + assertThat(res, is(notNullValue())); + assertThat(res.getDocuments().size(), is(2)); + + final Product newA = template.find(a.getId(), Product.class).get(); + assertThat(newA.getName(), is("aa")); + final Product newB = template.find(b.getId(), Product.class).get(); + assertThat(newB.getName(), is("bb")); + } + + @Test + public void deleteDocument() { + final DocumentEntity res = template.insert(new Customer("John", "Doe", 30)); + template.delete(res.getId(), Customer.class); + final Optional customer = template.find(res.getId(), Customer.class); + assertThat(customer.isPresent(), is(false)); + } + + @Test + public void deleteDocuments() { + final DocumentEntity a = template.insert(new Product("a")); + final DocumentEntity b = template.insert(new Product("b")); + + final Product documentA = template.find(a.getId(), Product.class).get(); + final Product documentB = template.find(b.getId(), Product.class).get(); + + final MultiDocumentEntity res = template.delete(Arrays.asList(documentA, documentB), + Product.class); + assertThat(res, is(notNullValue())); + assertThat(res.getDocuments().size(), is(2)); + + final Optional deletedA = template.find(a.getId(), Product.class); + assertThat(deletedA.isPresent(), is(false)); + final Optional deletedB = template.find(b.getId(), Product.class); + assertThat(deletedB.isPresent(), is(false)); + } + + @Test + public void query() { + template.insert(new Customer("John", "Doe", 30)); + final ArangoCursor cursor = template.query("FOR c IN @@coll FILTER c.name == @name RETURN c", + new MapBuilder().put("@coll", "customer").put("name", "John").get(), new AqlQueryOptions(), Customer.class); + assertThat(cursor, is(notNullValue())); + final List customers = cursor.asListRemaining(); + assertThat(customers.size(), is(1)); + assertThat(customers.get(0).getName(), is("John")); + assertThat(customers.get(0).getSurname(), is("Doe")); + assertThat(customers.get(0).getAge(), is(30)); + } + + @Test + public void queryWithoutBindParams() { + template.insert(new Customer("John", "Doe", 30)); + final ArangoCursor cursor = template.query("FOR c IN customer FILTER c.name == 'John' RETURN c", null, + new AqlQueryOptions(), Customer.class); + assertThat(cursor, is(notNullValue())); + final List customers = cursor.asListRemaining(); + assertThat(customers.size(), is(1)); + assertThat(customers.get(0).getName(), is("John")); + assertThat(customers.get(0).getSurname(), is("Doe")); + assertThat(customers.get(0).getAge(), is(30)); + } + + @SuppressWarnings("rawtypes") + @Test + public void queryMap() { + template.insert(new Customer("John", "Doe", 30)); + final ArangoCursor cursor = template.query("FOR c IN @@coll FILTER c.name == @name RETURN c", + new MapBuilder().put("@coll", "customer").put("name", "John").get(), new AqlQueryOptions(), Map.class); + assertThat(cursor, is(notNullValue())); + final List customers = cursor.asListRemaining(); + assertThat(customers.size(), is(1)); + assertThat(customers.get(0).get("name"), is("John")); + assertThat(customers.get(0).get("surname"), is("Doe")); + assertThat(customers.get(0).get("age"), is(30L)); + } + + @Test + public void queryBaseDocument() { + template.insert(new Customer("John", "Doe", 30)); + final ArangoCursor cursor = template.query("FOR c IN @@coll FILTER c.name == @name RETURN c", + new MapBuilder().put("@coll", "customer").put("name", "John").get(), new AqlQueryOptions(), + BaseDocument.class); + assertThat(cursor, is(notNullValue())); + final List customers = cursor.asListRemaining(); + assertThat(customers.size(), is(1)); + assertThat(customers.get(0).getAttribute("name"), is("John")); + assertThat(customers.get(0).getAttribute("surname"), is("Doe")); + assertThat(customers.get(0).getAttribute("age"), is(30L)); + } + + @Test + public void queryVPackSlice() { + template.insert(new Customer("John", "Doe", 30)); + final ArangoCursor cursor = template.query("FOR c IN @@coll FILTER c.name == @name RETURN c", + new MapBuilder().put("@coll", "customer").put("name", "John").get(), new AqlQueryOptions(), + VPackSlice.class); + assertThat(cursor, is(notNullValue())); + final List customers = cursor.asListRemaining(); + assertThat(customers.size(), is(1)); + assertThat(customers.get(0).get("name").getAsString(), is("John")); + assertThat(customers.get(0).get("surname").getAsString(), is("Doe")); + assertThat(customers.get(0).get("age").getAsInt(), is(30)); + } + + public static class NewEntityTest implements Persistable { + + private static final long serialVersionUID = 1L; + + @Id + private String id; + @Key + private final String key; + @Transient + private transient boolean persisted; + + public NewEntityTest(final String key) { + super(); + this.key = key; + } + + public void setPersisted(final boolean persisted) { + this.persisted = persisted; + } + + @Override + public boolean isNew() { + return !persisted; + } + + @Override + public String getId() { + return key; + } + } + + @Test + public void upsertWithUserGeneratedKey() { + final NewEntityTest entity = new NewEntityTest("test"); + template.upsert(entity, UpsertStrategy.REPLACE); + assertThat(template.collection(NewEntityTest.class).count(), is(1L)); + entity.setPersisted(true); + template.upsert(entity, UpsertStrategy.REPLACE); + assertThat(template.collection(NewEntityTest.class).count(), is(1L)); + } + + @Test + public void mutliUpsertWithUserGeneratedKey() { + final NewEntityTest entity1 = new NewEntityTest("test1"); + final NewEntityTest entity2 = new NewEntityTest("test2"); + template.upsert(Arrays.asList(entity1, entity2), UpsertStrategy.REPLACE); + assertThat(template.collection(NewEntityTest.class).count(), is(2L)); + entity1.setPersisted(true); + entity2.setPersisted(true); + template.upsert(Arrays.asList(entity1, entity2), UpsertStrategy.REPLACE); + assertThat(template.collection(NewEntityTest.class).count(), is(2L)); + } + +} diff --git a/src/test/java/com/arangodb/springframework/testdata/Customer.java b/src/test/java/com/arangodb/springframework/testdata/Customer.java index 28c12cf94..b8979f080 100644 --- a/src/test/java/com/arangodb/springframework/testdata/Customer.java +++ b/src/test/java/com/arangodb/springframework/testdata/Customer.java @@ -1,247 +1,248 @@ -/* - * 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.testdata; - -import java.util.Collection; - -import org.springframework.data.annotation.Id; - -import com.arangodb.springframework.annotation.Document; -import com.arangodb.springframework.annotation.GeoIndexed; -import com.arangodb.springframework.annotation.Key; -import com.arangodb.springframework.annotation.Ref; -import com.arangodb.springframework.annotation.Relations; -import com.arangodb.springframework.annotation.Rev; - -/** - * @author Mark Vollmary - * - */ -@Document -public class Customer { - - @Id - private String id; - @Key - private String key; - @Rev - private String rev; - private String name; - private String surname; - private int age; - private Address address; - - private boolean alive; - @GeoIndexed - private int[] location; - private Iterable integerList; - private String[] stringArray; - private Iterable stringList; - private Customer nestedCustomer; - private Iterable nestedCustomers; - - @Ref - private ShoppingCart shoppingCart; - @Relations(edges = { Owns.class }) - private Collection owns; - - public Customer() { - super(); - } - - public Customer(final String name, final String surname, final int age) { - super(); - this.name = name; - this.surname = surname; - this.age = age; - } - - public Customer(final String name, final String surname, final int age, final boolean alive) { - super(); - this.name = name; - this.surname = surname; - this.age = age; - this.alive = alive; - } - - public Customer(final String name, final String surname, final int age, final Address address) { - super(); - this.name = name; - this.surname = surname; - this.age = age; - this.address = address; - } - - public String getId() { - return id; - } - - public void setId(final String id) { - this.id = id; - } - - public String getKey() { - return key; - } - - public void setKey(final String key) { - this.key = key; - } - - public String getRev() { - return rev; - } - - public void setRev(final String rev) { - this.rev = rev; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - public String getSurname() { - return surname; - } - - public void setSurname(final String surname) { - this.surname = surname; - } - - public int getAge() { - return age; - } - - public void setAge(final int age) { - this.age = age; - } - - public Address getAddress() { - return address; - } - - public void setAddress(final Address address) { - this.address = address; - } - - public ShoppingCart getShoppingCart() { - return shoppingCart; - } - - public void setShoppingCart(final ShoppingCart shoppingCart) { - this.shoppingCart = shoppingCart; - } - - public Collection getOwns() { - return owns; - } - - public void setOwns(final Collection owns) { - this.owns = owns; - } - - public boolean isAlive() { - return alive; - } - - public void setAlive(final boolean alive) { - this.alive = alive; - } - - public int[] getLocation() { - return location; - } - - public void setLocation(final int[] location) { - this.location = location; - } - - public Iterable getIntegerList() { - return integerList; - } - - public void setIntegerList(final Iterable integerList) { - this.integerList = integerList; - } - - public String[] getStringArray() { - return stringArray; - } - - public void setStringArray(final String[] stringArray) { - this.stringArray = stringArray; - } - - public void setStringList(final Iterable stringList) { - this.stringList = stringList; - } - - public Customer getNestedCustomer() { - return nestedCustomer; - } - - public void setNestedCustomer(final Customer nestedCustomer) { - this.nestedCustomer = nestedCustomer; - } - - public Iterable getNestedCustomers() { - return nestedCustomers; - } - - public void setNestedCustomers(final Iterable nestedCustomers) { - this.nestedCustomers = nestedCustomers; - } - - public Iterable getStringList() { - return stringList; - } - - @Override - public boolean equals(final Object o) { - if (!(o instanceof Customer)) { - return false; - } - final Customer customer = (Customer) o; - if (!customer.getId().equals(this.getId())) { - return false; - } - if (!customer.getName().equals(this.getName())) { - return false; - } - if (!customer.getSurname().equals(this.getSurname())) { - return false; - } - if (customer.getAge() != this.getAge()) { - return false; - } - return true; - } - - @Override - public String toString() { - return "Customer {id: " + id + ", name: " + name + ", surname: " + surname + ", age: " + age + "}"; - } - -} +/* + * 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.testdata; + +import java.util.Collection; + +import org.springframework.data.annotation.Id; + +import com.arangodb.springframework.annotation.Document; +import com.arangodb.springframework.annotation.GeoIndexed; +import com.arangodb.springframework.annotation.Key; +import com.arangodb.springframework.annotation.Ref; +import com.arangodb.springframework.annotation.Relations; +import com.arangodb.springframework.annotation.Rev; + +/** + * @author Mark Vollmary + * + */ +@SuppressWarnings("deprecation") +@Document +public class Customer { + + @Id + private String id; + @Key + private String key; + @Rev + private String rev; + private String name; + private String surname; + private int age; + private Address address; + + private boolean alive; + @GeoIndexed + private int[] location; + private Iterable integerList; + private String[] stringArray; + private Iterable stringList; + private Customer nestedCustomer; + private Iterable nestedCustomers; + + @Ref + private ShoppingCart shoppingCart; + @Relations(edges = { Owns.class }) + private Collection owns; + + public Customer() { + super(); + } + + public Customer(final String name, final String surname, final int age) { + super(); + this.name = name; + this.surname = surname; + this.age = age; + } + + public Customer(final String name, final String surname, final int age, final boolean alive) { + super(); + this.name = name; + this.surname = surname; + this.age = age; + this.alive = alive; + } + + public Customer(final String name, final String surname, final int age, final Address address) { + super(); + this.name = name; + this.surname = surname; + this.age = age; + this.address = address; + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(final String key) { + this.key = key; + } + + public String getRev() { + return rev; + } + + public void setRev(final String rev) { + this.rev = rev; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getSurname() { + return surname; + } + + public void setSurname(final String surname) { + this.surname = surname; + } + + public int getAge() { + return age; + } + + public void setAge(final int age) { + this.age = age; + } + + public Address getAddress() { + return address; + } + + public void setAddress(final Address address) { + this.address = address; + } + + public ShoppingCart getShoppingCart() { + return shoppingCart; + } + + public void setShoppingCart(final ShoppingCart shoppingCart) { + this.shoppingCart = shoppingCart; + } + + public Collection getOwns() { + return owns; + } + + public void setOwns(final Collection owns) { + this.owns = owns; + } + + public boolean isAlive() { + return alive; + } + + public void setAlive(final boolean alive) { + this.alive = alive; + } + + public int[] getLocation() { + return location; + } + + public void setLocation(final int[] location) { + this.location = location; + } + + public Iterable getIntegerList() { + return integerList; + } + + public void setIntegerList(final Iterable integerList) { + this.integerList = integerList; + } + + public String[] getStringArray() { + return stringArray; + } + + public void setStringArray(final String[] stringArray) { + this.stringArray = stringArray; + } + + public void setStringList(final Iterable stringList) { + this.stringList = stringList; + } + + public Customer getNestedCustomer() { + return nestedCustomer; + } + + public void setNestedCustomer(final Customer nestedCustomer) { + this.nestedCustomer = nestedCustomer; + } + + public Iterable getNestedCustomers() { + return nestedCustomers; + } + + public void setNestedCustomers(final Iterable nestedCustomers) { + this.nestedCustomers = nestedCustomers; + } + + public Iterable getStringList() { + return stringList; + } + + @Override + public boolean equals(final Object o) { + if (!(o instanceof Customer)) { + return false; + } + final Customer customer = (Customer) o; + if (!customer.getId().equals(this.getId())) { + return false; + } + if (!customer.getName().equals(this.getName())) { + return false; + } + if (!customer.getSurname().equals(this.getSurname())) { + return false; + } + if (customer.getAge() != this.getAge()) { + return false; + } + return true; + } + + @Override + public String toString() { + return "Customer {id: " + id + ", name: " + name + ", surname: " + surname + ", age: " + age + "}"; + } + +} diff --git a/src/test/java/com/arangodb/springframework/testdata/IncompleteCustomer.java b/src/test/java/com/arangodb/springframework/testdata/IncompleteCustomer.java index 9a208ff4e..ae515a2f8 100644 --- a/src/test/java/com/arangodb/springframework/testdata/IncompleteCustomer.java +++ b/src/test/java/com/arangodb/springframework/testdata/IncompleteCustomer.java @@ -8,6 +8,7 @@ import com.arangodb.springframework.annotation.Key; import com.arangodb.springframework.annotation.Rev; +@SuppressWarnings("deprecation") @Document("customer") public class IncompleteCustomer { @Id diff --git a/src/test/java/com/arangodb/springframework/testdata/Material.java b/src/test/java/com/arangodb/springframework/testdata/Material.java index d7f0dbed7..2f78229a9 100644 --- a/src/test/java/com/arangodb/springframework/testdata/Material.java +++ b/src/test/java/com/arangodb/springframework/testdata/Material.java @@ -1,31 +1,33 @@ package com.arangodb.springframework.testdata; +import org.springframework.data.annotation.Id; + import com.arangodb.springframework.annotation.Key; import com.arangodb.springframework.annotation.Rev; -import org.springframework.data.annotation.Id; /** * Created by markmccormick on 24/08/2017. */ +@SuppressWarnings("deprecation") public class Material { - @Id - private String id; - @Key - private String key; - @Rev - private String rev; - private String name; + @Id + private String id; + @Key + private String key; + @Rev + private String rev; + private String name; - public Material(String name) { - this.name = name; - } + public Material(final String name) { + this.name = name; + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public void setName(String name) { - this.name = name; - } + public void setName(final String name) { + this.name = name; + } } diff --git a/src/test/java/com/arangodb/springframework/testdata/Product.java b/src/test/java/com/arangodb/springframework/testdata/Product.java index a81bd9daa..d1aaff4e8 100644 --- a/src/test/java/com/arangodb/springframework/testdata/Product.java +++ b/src/test/java/com/arangodb/springframework/testdata/Product.java @@ -1,132 +1,133 @@ -/* - * 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.testdata; - -import org.springframework.data.annotation.Id; - -import com.arangodb.springframework.annotation.Field; -import com.arangodb.springframework.annotation.GeoIndex; -import com.arangodb.springframework.annotation.Key; -import com.arangodb.springframework.annotation.Relations; -import com.arangodb.springframework.annotation.Rev; - -/** - * @author Mark Vollmary - * - */ -@GeoIndex(fields = { "location" }) -public class Product { - - @Id - private String id; - @Key - private String key; - @Rev - private String rev; - private String name; - @Field("description") - private String desc; - private double[] location; - - private Product nested; - - @Relations(edges = { Contains.class }) - private Material contains; - - public Product() { - super(); - } - - public Product(final String name) { - super(); - this.name = name; - } - - public Product(final String name, final double[] location) { - this(name); - this.location = location; - } - - public String getId() { - return id; - } - - public void setId(final String id) { - this.id = id; - } - - public String getKey() { - return key; - } - - public void setKey(final String key) { - this.key = key; - } - - public String getRev() { - return rev; - } - - public void setRev(final String rev) { - this.rev = rev; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - public String getDesc() { - return desc; - } - - public void setDesc(final String desc) { - this.desc = desc; - } - - public Material getContains() { - return contains; - } - - public void setContains(final Material contains) { - this.contains = contains; - } - - public double[] getLocation() { - return location; - } - - public void setLocation(final double[] location) { - this.location = location; - } - - public Product getNested() { - return nested; - } - - public void setNested(final Product nested) { - this.nested = nested; - } - -} +/* + * 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.testdata; + +import org.springframework.data.annotation.Id; + +import com.arangodb.springframework.annotation.Field; +import com.arangodb.springframework.annotation.GeoIndex; +import com.arangodb.springframework.annotation.Key; +import com.arangodb.springframework.annotation.Relations; +import com.arangodb.springframework.annotation.Rev; + +/** + * @author Mark Vollmary + * + */ +@SuppressWarnings("deprecation") +@GeoIndex(fields = { "location" }) +public class Product { + + @Id + private String id; + @Key + private String key; + @Rev + private String rev; + private String name; + @Field("description") + private String desc; + private double[] location; + + private Product nested; + + @Relations(edges = { Contains.class }) + private Material contains; + + public Product() { + super(); + } + + public Product(final String name) { + super(); + this.name = name; + } + + public Product(final String name, final double[] location) { + this(name); + this.location = location; + } + + public String getId() { + return id; + } + + public void setId(final String id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(final String key) { + this.key = key; + } + + public String getRev() { + return rev; + } + + public void setRev(final String rev) { + this.rev = rev; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + public String getDesc() { + return desc; + } + + public void setDesc(final String desc) { + this.desc = desc; + } + + public Material getContains() { + return contains; + } + + public void setContains(final Material contains) { + this.contains = contains; + } + + public double[] getLocation() { + return location; + } + + public void setLocation(final double[] location) { + this.location = location; + } + + public Product getNested() { + return nested; + } + + public void setNested(final Product nested) { + this.nested = nested; + } + +} From 95c6576ccfa44d6735e30cd37453b0b3c2f7d791 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Wed, 18 Jul 2018 15:17:15 +0200 Subject: [PATCH 84/94] Prepare release 1.3.0 --- ChangeLog.md | 2 ++ pom.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 839c1f636..22f6725de 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [1.3.0] - 2018-07-18 + ### Deprecated - deprecated `com.arangodb.springframework.annotation.Key` diff --git a/pom.xml b/pom.xml index d6c6acbbc..bd94cb908 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.3.0-SNAPSHOT + 1.3.0 2017 jar From 1df921b4d77c9e8228d4eb2a1ad356119b4706a0 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Wed, 18 Jul 2018 15:24:50 +0200 Subject: [PATCH 85/94] Prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bd94cb908..e784bfb68 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.3.0 + 1.3.1-SNAPSHOT 2017 jar From 8d7d437bf82156d605cfeedd9989a8d72e30153a Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 10 Aug 2018 09:49:03 +0200 Subject: [PATCH 86/94] Fix a bug in derived queries when using two times `@relations` in one entity --- ChangeLog.md | 4 + .../query/derived/DerivedQueryCreator.java | 129 +++++++++--------- .../repository/CustomerRepository.java | 2 + .../derived/DerivedQueryCreatorTest.java | 30 ++++ .../springframework/testdata/Customer.java | 11 ++ 5 files changed, 114 insertions(+), 62 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 22f6725de..f831e8204 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +### Fixed + +- fixed a bug in derived queries when using two times `@Relations` in one entity + ## [1.3.0] - 2018-07-18 ### Deprecated diff --git a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java index b5a56c505..baef94d8c 100644 --- a/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java +++ b/src/main/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreator.java @@ -87,13 +87,12 @@ public class DerivedQueryCreator extends AbstractQueryCreator, ArangoPersistentProperty> context, - final Class domainClass, final PartTree tree, final ArangoParameterAccessor accessor, - final Map bindVars, final List geoFields, final boolean useFunctions) { + final MappingContext, ArangoPersistentProperty> context, + final Class domainClass, final PartTree tree, final ArangoParameterAccessor accessor, + final Map bindVars, final List geoFields, final boolean useFunctions) { super(tree, accessor); this.context = context; this.collectionName = collectionName(context.getPersistentEntity(domainClass).getCollection()); @@ -144,8 +143,8 @@ protected ConjunctionBuilder or(final ConjunctionBuilder base, final Conjunction } /** - * Builds a full AQL query from a built Disjunction, additional information from - * PartTree and special parameters caught by ArangoParameterAccessor + * Builds a full AQL query from a built Disjunction, additional information from PartTree and special parameters + * caught by ArangoParameterAccessor * * @param criteria * @param sort @@ -161,19 +160,19 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { } final Disjunction disjunction = disjunctionBuilder.build(); final String array = disjunction.getArray().length() == 0 ? collectionName : disjunction.getArray(); - final String predicate = disjunction.getPredicate().length() == 0 ? "" : " FILTER " + disjunction.getPredicate(); + final String predicate = disjunction.getPredicate().length() == 0 ? "" + : " FILTER " + disjunction.getPredicate(); final String queryTemplate = "%sFOR e IN %s%s%s%s%s%s%s"; // collection predicate count sort limit pageable // queryType final String count = (tree.isCountProjection() || tree.isExistsProjection()) - ? ((tree.isDistinct() ? " COLLECT entity = e" : "") + " COLLECT WITH COUNT INTO length") - : ""; + ? ((tree.isDistinct() ? " COLLECT entity = e" : "") + " COLLECT WITH COUNT INTO length") : ""; final String limit = tree.isLimiting() ? format(" LIMIT %d", tree.getMaxResults()) : ""; final String pageable = accessor.getPageable() == null ? "" : format(" LIMIT %d, %d", accessor.getPageable().getOffset(), accessor.getPageable().getPageSize()); final String geoFields = format("%s[0], %s[1]", uniqueLocation, uniqueLocation); final String distanceAdjusted = getGeoFields().isEmpty() ? "e" : format("MERGE(e, { '_distance': distance(%s, %f, %f) })", geoFields, getUniquePoint()[0], - getUniquePoint()[1]); + getUniquePoint()[1]); final String type = tree.isDelete() ? (" REMOVE e IN " + collectionName) : ((tree.isCountProjection() || tree.isExistsProjection()) ? " RETURN length" : format(" RETURN %s", distanceAdjusted)); @@ -181,7 +180,7 @@ protected String complete(final ConjunctionBuilder criteria, final Sort sort) { if ((!this.geoFields.isEmpty() || isUnique != null && isUnique) && !tree.isDelete() && !tree.isCountProjection() && !tree.isExistsProjection()) { final String distanceSortKey = format(" SORT distance(%s, %f, %f)", geoFields, getUniquePoint()[0], - getUniquePoint()[1]); + getUniquePoint()[1]); if (sort == null) { sortString = distanceSortKey; } else { @@ -218,8 +217,7 @@ private String ignorePropertyCase(final Part part) { } /** - * Wrapps property expression in order to lower case. Only properties of type - * String or Iterable are lowered + * Wrapps property expression in order to lower case. Only properties of type String or Iterable are lowered * * @param part * @param property @@ -242,20 +240,20 @@ private String ignorePropertyCase(final Part part, final String property) { * @return */ private String getProperty(final Part part) { - return "e." - + context.getPersistentPropertyPath(part.getProperty()).toPath(null, ArangoPersistentProperty::getFieldName); + return "e." + context.getPersistentPropertyPath(part.getProperty()).toPath(null, + ArangoPersistentProperty::getFieldName); } /** - * Creates a predicate template with one String placeholder for a Part-specific - * predicate expression from properties in PropertyPath which represent - * references or collections, and, also, returns a 2nd String representing - * property to be used in a Part-specific predicate expression + * Creates a predicate template with one String placeholder for a Part-specific predicate expression from properties + * in PropertyPath which represent references or collections, and, also, returns a 2nd String representing property + * to be used in a Part-specific predicate expression * * @param part + * @param varsUsed * @return */ - private String[] createPredicateTemplateAndPropertyString(final Part part) { + private String[] createPredicateTemplateAndPropertyString(final Part part, int varsUsed) { final String PREDICATE_TEMPLATE = "(%s FILTER %%s RETURN 1)[0] == 1"; final PersistentPropertyPath persistentPropertyPath = context.getPersistentPropertyPath(part.getProperty()); StringBuilder simpleProperties = new StringBuilder(); @@ -305,7 +303,8 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { simpleProperties = new StringBuilder(); final String iteration = format(TEMPLATE, entity, collection, entity, prevEntity, name); final String predicate = format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); + predicateTemplate = predicateTemplate.length() == 0 ? predicate + : format(predicateTemplate, predicate); } else { // collection final String TEMPLATE = "FOR %s IN TO_ARRAY(%s%s)"; @@ -315,7 +314,8 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { simpleProperties = new StringBuilder(); final String iteration = format(TEMPLATE, entity, prevEntity, name); final String predicate = format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); + predicateTemplate = predicateTemplate.length() == 0 ? predicate + : format(predicateTemplate, predicate); } } else { if (property.getRef().isPresent() || property.getFrom().isPresent() || property.getTo().isPresent()) { @@ -331,7 +331,8 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { simpleProperties = new StringBuilder(); final String iteration = format(TEMPLATE, entity, collection, entity, prevEntity, name); final String predicate = format(PREDICATE_TEMPLATE, iteration); - predicateTemplate = predicateTemplate.length() == 0 ? predicate : format(predicateTemplate, predicate); + predicateTemplate = predicateTemplate.length() == 0 ? predicate + : format(predicateTemplate, predicate); } else { // simple property simpleProperties.append("." + property.getFieldName()); @@ -343,8 +344,7 @@ private String[] createPredicateTemplateAndPropertyString(final Part part) { } /** - * Lowers case of a given argument if its type is String, Iterable or - * String[] if shouldIgnoreCase is true + * Lowers case of a given argument if its type is String, Iterable or String[] if shouldIgnoreCase is true * * @param argument * @param shouldIgnoreCase @@ -374,8 +374,8 @@ private Object ignoreArgumentCase(final Object argument, final boolean shouldIgn } /** - * Determines whether the case for a Part should be ignored based on property - * type and IgnoreCase keywords in the method name + * Determines whether the case for a Part should be ignored based on property type and IgnoreCase keywords in the + * method name * * @param part * @return @@ -393,8 +393,7 @@ private boolean shouldIgnoreCase(final Part part) { } /** - * Puts actual arguments in bindVars Map based on Part-specific information and - * types of arguments. + * Puts actual arguments in bindVars Map based on Part-specific information and types of arguments. * * @param iterator * @param shouldIgnoreCase @@ -403,8 +402,12 @@ private boolean shouldIgnoreCase(final Part part) { * @param ignoreBindVars * @return */ - private ArgumentProcessingResult bindArguments(final Iterator iterator, final boolean shouldIgnoreCase, - final int arguments, final Boolean borderStatus, final boolean ignoreBindVars) { + private ArgumentProcessingResult bindArguments( + final Iterator iterator, + final boolean shouldIgnoreCase, + final int arguments, + final Boolean borderStatus, + final boolean ignoreBindVars) { int bindings = 0; ArgumentProcessingResult.Type type = ArgumentProcessingResult.Type.DEFAULT; for (int i = 0; i < arguments; ++i) { @@ -450,7 +453,8 @@ private ArgumentProcessingResult bindArguments(final Iterator iterator, checkUniquePoint(circle.getCenter()); bindVars.put(Integer.toString(bindingCounter + bindings++), circle.getCenter().getY()); bindVars.put(Integer.toString(bindingCounter + bindings++), circle.getCenter().getX()); - bindVars.put(Integer.toString(bindingCounter + bindings++), convertDistanceToMeters(circle.getRadius())); + bindVars.put(Integer.toString(bindingCounter + bindings++), + convertDistanceToMeters(circle.getRadius())); break; } else if (caseAdjusted.getClass() == Point.class) { final Point point = (Point) caseAdjusted; @@ -481,8 +485,8 @@ private ArgumentProcessingResult bindArguments(final Iterator iterator, } /** - * Ensures that Points used in geospatial parts of non-nested properties are the - * same in case geospatial return type is expected + * Ensures that Points used in geospatial parts of non-nested properties are the same in case geospatial return type + * is expected * * @param point */ @@ -496,7 +500,7 @@ private void checkUniquePoint(final Point point) { } if (!geoFields.isEmpty()) { Assert.isTrue(uniquePoint == null || uniquePoint.equals(point), - "Different Points are used - Distance is ambiguous"); + "Different Points are used - Distance is ambiguous"); uniquePoint = point; } } @@ -518,8 +522,8 @@ private double convertDistanceToMeters(final Distance distance) { } /** - * Ensures that the same geo fields are used in geospatial parts of non-nested - * properties are the same in case geospatial return type is expected + * Ensures that the same geo fields are used in geospatial parts of non-nested properties are the same in case + * geospatial return type is expected * * @param part */ @@ -536,15 +540,16 @@ private void checkUniqueLocation(final Part part) { } /** - * Creates a PartInformation containing a String representing either a predicate - * or array expression, and binds arguments from Iterator for a given Part + * Creates a PartInformation containing a String representing either a predicate or array expression, and binds + * arguments from Iterator for a given Part * * @param part * @param iterator * @return */ private PartInformation createPartInformation(final Part part, final Iterator iterator) { - final String[] templateAndProperty = createPredicateTemplateAndPropertyString(part); + int varsUsed = 0; + final String[] templateAndProperty = createPredicateTemplateAndPropertyString(part, varsUsed); final String template = templateAndProperty[0]; final String property = templateAndProperty[1]; boolean isArray = false; @@ -591,7 +596,7 @@ private PartInformation createPartInformation(final Part part, final Iterator context.getPersistentEntity(t) != null).map(t -> t.getType()).ifPresent(t -> with.add(t)); + .filter(t -> context.getPersistentEntity(t) != null).map(t -> t.getType()) + .ifPresent(t -> with.add(t)); } while ((pp = pp.next()) != null); return clause == null ? null : new PartInformation(isArray, clause, with); } @@ -776,8 +782,7 @@ private String format(final String format, final Object... args) { } /** - * Stores how many bindings where used in a Part and if or what kind of special - * type clause should be created + * Stores how many bindings where used in a Part and if or what kind of special type clause should be created */ private static class ArgumentProcessingResult { diff --git a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java index ef516663d..69d1257ab 100644 --- a/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java +++ b/src/test/java/com/arangodb/springframework/repository/CustomerRepository.java @@ -196,6 +196,8 @@ List findByNestedCustomersNestedCustomerShoppingCartProductsLocationWi List getByOwnsName(String name); + List getByOwnsNameAndOwns2Name(String name, String name2); + List getByOwnsContainsName(String name); // Count query diff --git a/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java b/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java index eb74da997..94e3883f7 100644 --- a/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java +++ b/src/test/java/com/arangodb/springframework/repository/query/derived/DerivedQueryCreatorTest.java @@ -736,6 +736,36 @@ public void relationsSingleLevelTest() { assertTrue(equals(toBeRetrieved, retrieved, cmp, eq, false)); } + @Test + public void twoRelationsSingleLevelTest() { + final List toBeRetrieved = new LinkedList<>(); + final List customers = new LinkedList<>(); + List retrieved; + final Customer john = new Customer("John", "Smith", 52); + final Customer adam = new Customer("Adam", "Smith", 294); + final Customer matt = new Customer("Matt", "Smith", 34); + final Product phone = new Product("phone"); + final Product car = new Product("car"); + final Product chair = new Product("chair"); + template.insert(phone); + template.insert(car); + template.insert(chair); + customers.add(john); + customers.add(matt); + customers.add(adam); + repository.save(customers); + template.insert(new Owns(john, phone)); + template.insert(new Owns(john, car)); + template.insert(new Owns(adam, chair)); + template.insert(new Owns(matt, phone)); + template.insert(new Owns(matt, car)); + template.insert(new Owns(matt, chair)); + toBeRetrieved.add(john); + toBeRetrieved.add(matt); + retrieved = repository.getByOwnsNameAndOwns2Name(phone.getName(), phone.getName()); + assertTrue(equals(toBeRetrieved, retrieved, cmp, eq, false)); + } + @Test public void relationsMultiLevelTest() { final List toBeRetrieved = new LinkedList<>(); diff --git a/src/test/java/com/arangodb/springframework/testdata/Customer.java b/src/test/java/com/arangodb/springframework/testdata/Customer.java index b8979f080..162b9261a 100644 --- a/src/test/java/com/arangodb/springframework/testdata/Customer.java +++ b/src/test/java/com/arangodb/springframework/testdata/Customer.java @@ -64,6 +64,9 @@ public class Customer { @Relations(edges = { Owns.class }) private Collection owns; + @Relations(edges = { Owns.class }) + private Collection owns2; + public Customer() { super(); } @@ -163,6 +166,14 @@ public void setOwns(final Collection owns) { this.owns = owns; } + public Collection getOwns2() { + return owns2; + } + + public void setOwns2(final Collection owns2) { + this.owns2 = owns2; + } + public boolean isAlive() { return alive; } From 64aeab5dedea33b556a2a07007e480d23c99930b Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 10 Aug 2018 09:55:35 +0200 Subject: [PATCH 87/94] Prepare release 1.3.1 --- ChangeLog.md | 2 ++ pom.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index f831e8204..bf1e430bc 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [1.3.1] - 2018-08-10 + ### Fixed - fixed a bug in derived queries when using two times `@Relations` in one entity diff --git a/pom.xml b/pom.xml index e784bfb68..170d94899 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.3.1-SNAPSHOT + 1.3.1 2017 jar From 35ea987de3ae234a2ec4ef277498b1fdf4e78d2e Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Fri, 10 Aug 2018 11:15:58 +0200 Subject: [PATCH 88/94] Prepare snapshot --- ChangeLog.md | 2 -- pom.xml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index bf1e430bc..f831e8204 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,8 +6,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] -## [1.3.1] - 2018-08-10 - ### Fixed - fixed a bug in derived queries when using two times `@Relations` in one entity diff --git a/pom.xml b/pom.xml index 170d94899..596642093 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.3.1 + 1.3.1-SNASPHOT 2017 jar From 9d0eff00ee6c2cf9c301440ad2931a820b568541 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 13 Aug 2018 07:22:58 +0200 Subject: [PATCH 89/94] Prepare release 1.3.1 --- ChangeLog.md | 2 ++ pom.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index f831e8204..544e394a5 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Fixed +## [1.3.1] - 2018-08-13 + - fixed a bug in derived queries when using two times `@Relations` in one entity ## [1.3.0] - 2018-07-18 diff --git a/pom.xml b/pom.xml index 596642093..170d94899 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.3.1-SNASPHOT + 1.3.1 2017 jar From 40682114d5917a7a918877d2c18c8c48d64bd0ec Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 13 Aug 2018 07:33:57 +0200 Subject: [PATCH 90/94] Prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 170d94899..f6bff4b60 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.3.1 + 1.3.2-SNAPSHOT 2017 jar From bf496c319e45f4758bde1021f941b1f1de6c5f4d Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 13 Aug 2018 10:34:52 +0200 Subject: [PATCH 91/94] Fix repository methods with `Example` using `StringMatcher.CONTAINING` (issue #113) --- ChangeLog.md | 4 ++++ .../repository/ArangoExampleConverter.java | 3 +++ .../repository/ArangoRepositoryTest.java | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 544e394a5..0cea24171 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -8,8 +8,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ### Fixed +- fixed repository methods with `Example` using `StringMatcher.CONTAINING` (issue #113) + ## [1.3.1] - 2018-08-13 +### Fixed + - fixed a bug in derived queries when using two times `@Relations` in one entity ## [1.3.0] - 2018-07-18 diff --git a/src/main/java/com/arangodb/springframework/repository/ArangoExampleConverter.java b/src/main/java/com/arangodb/springframework/repository/ArangoExampleConverter.java index fcaa8c23f..0faa048b5 100644 --- a/src/main/java/com/arangodb/springframework/repository/ArangoExampleConverter.java +++ b/src/main/java/com/arangodb/springframework/repository/ArangoExampleConverter.java @@ -114,6 +114,9 @@ private void addPredicate( case ENDING: value = escape(string) + "$"; break; + case CONTAINING: + value = escape(string); + break; case DEFAULT: case EXACT: default: diff --git a/src/test/java/com/arangodb/springframework/repository/ArangoRepositoryTest.java b/src/test/java/com/arangodb/springframework/repository/ArangoRepositoryTest.java index c663f66e7..55882c557 100644 --- a/src/test/java/com/arangodb/springframework/repository/ArangoRepositoryTest.java +++ b/src/test/java/com/arangodb/springframework/repository/ArangoRepositoryTest.java @@ -1,7 +1,9 @@ package com.arangodb.springframework.repository; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.util.ArrayList; @@ -12,6 +14,7 @@ import org.junit.runner.RunWith; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; +import org.springframework.data.domain.ExampleMatcher.StringMatcher; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; @@ -324,4 +327,19 @@ public void endingWithByExampleNestedIncludeNullTest() { final Customer retrieved = repository.findOne(example); assertEquals(check, retrieved); } + + @Test + public void containingExampleTest() { + final Customer entity = new Customer("name", "surname", 10); + repository.save(entity); + + final Customer probe = new Customer(); + probe.setName("am"); + final Example example = Example.of(probe, + ExampleMatcher.matching().withStringMatcher(StringMatcher.CONTAINING).withIgnorePaths("arangoId", "id", + "key", "rev", "surname", "age")); + final Optional retrieved = repository.findOne(example); + assertThat(retrieved.isPresent(), is(true)); + assertThat(retrieved.get().getName(), is("name")); + } } From f51e7d72d92d4ae398efa995708afbcd53af2fa0 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Mon, 13 Aug 2018 15:06:21 +0200 Subject: [PATCH 92/94] Fix test --- .../springframework/repository/ArangoRepositoryTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/arangodb/springframework/repository/ArangoRepositoryTest.java b/src/test/java/com/arangodb/springframework/repository/ArangoRepositoryTest.java index 55882c557..d1881a54a 100644 --- a/src/test/java/com/arangodb/springframework/repository/ArangoRepositoryTest.java +++ b/src/test/java/com/arangodb/springframework/repository/ArangoRepositoryTest.java @@ -1,6 +1,7 @@ package com.arangodb.springframework.repository; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; @@ -338,8 +339,8 @@ public void containingExampleTest() { final Example example = Example.of(probe, ExampleMatcher.matching().withStringMatcher(StringMatcher.CONTAINING).withIgnorePaths("arangoId", "id", "key", "rev", "surname", "age")); - final Optional retrieved = repository.findOne(example); - assertThat(retrieved.isPresent(), is(true)); - assertThat(retrieved.get().getName(), is("name")); + final Customer retrieved = repository.findOne(example); + assertThat(retrieved, is(notNullValue())); + assertThat(retrieved.getName(), is("name")); } } From 2862ef2972460c557b44a20781db3681cb7960b6 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 4 Sep 2018 09:49:17 +0200 Subject: [PATCH 93/94] Prepare release 1.3.2 --- ChangeLog.md | 2 ++ pom.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 0cea24171..0499ccaea 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +## [1.3.2] - 2018-09-04 + ### Fixed - fixed repository methods with `Example` using `StringMatcher.CONTAINING` (issue #113) diff --git a/pom.xml b/pom.xml index f6bff4b60..591a9ce01 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.3.2-SNAPSHOT + 1.3.2 2017 jar From 7de6edd6dfaac66f137345e2930fcb642a8ad8e0 Mon Sep 17 00:00:00 2001 From: mpv1989 Date: Tue, 4 Sep 2018 09:52:34 +0200 Subject: [PATCH 94/94] Prepare snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 591a9ce01..04beae57b 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.arangodb arangodb-spring-data - 1.3.2 + 1.3.3-SNAPSHOT 2017 jar