Skip to content

Commit 7903c19

Browse files
authored
Add @ArangoId (representation of _id) (#101)
1 parent 0ef0f2c commit 7903c19

File tree

13 files changed

+277
-6
lines changed

13 files changed

+277
-6
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* DISCLAIMER
3+
*
4+
* Copyright 2018 ArangoDB GmbH, Cologne, Germany
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
* Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
*/
20+
21+
package com.arangodb.springframework.annotation;
22+
23+
import java.lang.annotation.ElementType;
24+
import java.lang.annotation.Retention;
25+
import java.lang.annotation.RetentionPolicy;
26+
import java.lang.annotation.Target;
27+
28+
import org.springframework.data.annotation.ReadOnlyProperty;
29+
30+
/**
31+
* Representation of ArangoDB document field {@code _id}
32+
*
33+
* @author Mark Vollmary
34+
*/
35+
@Retention(RetentionPolicy.RUNTIME)
36+
@Target({ ElementType.FIELD })
37+
@ReadOnlyProperty
38+
public @interface ArangoId {
39+
40+
}

src/main/java/com/arangodb/springframework/core/convert/DefaultArangoConverter.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,11 +624,24 @@ private void writeEntity(
624624
}
625625
});
626626

627+
addKeyIfNecessary(entity, source, sink);
627628
addTypeKeyIfNecessary(definedType, source, sink);
628629

629630
sink.close();
630631
}
631632

633+
private void addKeyIfNecessary(
634+
final ArangoPersistentEntity<?> entity,
635+
final Object source,
636+
final VPackBuilder sink) {
637+
if (!entity.hasIdProperty() || entity.getIdentifierAccessor(source).getIdentifier() == null) {
638+
final Object id = entity.getArangoIdAccessor(source).getIdentifier();
639+
if (id != null) {
640+
sink.add(_KEY, MetadataUtils.determineDocumentKeyFromId((String) id));
641+
}
642+
}
643+
}
644+
632645
private void writeProperty(final Object source, final VPackBuilder sink, final ArangoPersistentProperty property) {
633646
if (source == null) {
634647
return;
@@ -859,7 +872,11 @@ private Optional<String> getRefId(final Object source, final ArangoPersistentEnt
859872
}
860873

861874
final Optional<Object> id = Optional.ofNullable(entity.getIdentifierAccessor(source).getIdentifier());
862-
return id.map(key -> MetadataUtils.createIdFromCollectionAndKey(entity.getCollection(), convertId(key)));
875+
if (id.isPresent()) {
876+
return id.map(key -> MetadataUtils.createIdFromCollectionAndKey(entity.getCollection(), convertId(key)));
877+
}
878+
879+
return Optional.ofNullable((String) entity.getArangoIdAccessor(source).getIdentifier());
863880
}
864881

865882
private static Collection<?> asCollection(final Object source) {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* DISCLAIMER
3+
*
4+
* Copyright 2018 ArangoDB GmbH, Cologne, Germany
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
* Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
*/
20+
21+
package com.arangodb.springframework.core.mapping;
22+
23+
import java.util.Optional;
24+
25+
import org.springframework.data.mapping.PersistentPropertyAccessor;
26+
import org.springframework.data.mapping.TargetAwareIdentifierAccessor;
27+
import org.springframework.util.Assert;
28+
29+
/**
30+
* @author Mark Vollmary
31+
*
32+
*/
33+
public class ArangoIdPropertyIdentifierAccessor extends TargetAwareIdentifierAccessor {
34+
35+
private final ArangoPersistentProperty arangoIdProperty;
36+
private final PersistentPropertyAccessor accessor;
37+
38+
public ArangoIdPropertyIdentifierAccessor(final ArangoPersistentEntity<?> entity, final Object target) {
39+
super(target);
40+
41+
Assert.notNull(entity, "PersistentEntity must not be null!");
42+
final Optional<ArangoPersistentProperty> aip = entity.getArangoIdProperty();
43+
Assert.isTrue(aip.isPresent(), "PersistentEntity must have an arango identifier property!");
44+
Assert.notNull(target, "Target bean must not be null!");
45+
46+
this.arangoIdProperty = aip.get();
47+
this.accessor = entity.getPropertyAccessor(target);
48+
}
49+
50+
@Override
51+
public Object getIdentifier() {
52+
return accessor.getProperty(arangoIdProperty);
53+
}
54+
55+
}

src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentEntity.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Optional;
2525

2626
import org.springframework.context.ApplicationContextAware;
27+
import org.springframework.data.mapping.IdentifierAccessor;
2728
import org.springframework.data.mapping.PersistentEntity;
2829

2930
import com.arangodb.model.CollectionCreateOptions;
@@ -45,6 +46,8 @@ public interface ArangoPersistentEntity<T>
4546

4647
CollectionCreateOptions getCollectionOptions();
4748

49+
Optional<ArangoPersistentProperty> getArangoIdProperty();
50+
4851
Optional<ArangoPersistentProperty> getRevProperty();
4952

5053
Collection<HashIndex> getHashIndexes();
@@ -67,4 +70,6 @@ public interface ArangoPersistentEntity<T>
6770

6871
Collection<ArangoPersistentProperty> getFulltextIndexedProperties();
6972

73+
IdentifierAccessor getArangoIdAccessor(Object bean);
74+
7075
}

src/main/java/com/arangodb/springframework/core/mapping/ArangoPersistentProperty.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public interface ArangoPersistentProperty extends PersistentProperty<ArangoPersi
4242

4343
String getFieldName();
4444

45+
boolean isArangoIdProperty();
46+
4547
boolean isRevProperty();
4648

4749
Optional<Ref> getRef();

src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentEntity.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,15 @@
3636
import org.springframework.context.expression.BeanFactoryAccessor;
3737
import org.springframework.context.expression.BeanFactoryResolver;
3838
import org.springframework.core.annotation.AnnotatedElementUtils;
39+
import org.springframework.data.mapping.IdentifierAccessor;
40+
import org.springframework.data.mapping.TargetAwareIdentifierAccessor;
3941
import org.springframework.data.mapping.model.BasicPersistentEntity;
4042
import org.springframework.data.util.TypeInformation;
4143
import org.springframework.expression.Expression;
4244
import org.springframework.expression.ParserContext;
4345
import org.springframework.expression.spel.standard.SpelExpressionParser;
4446
import org.springframework.expression.spel.support.StandardEvaluationContext;
47+
import org.springframework.lang.Nullable;
4548
import org.springframework.util.StringUtils;
4649

4750
import com.arangodb.entity.CollectionType;
@@ -72,6 +75,7 @@ public class DefaultArangoPersistentEntity<T> extends BasicPersistentEntity<T, A
7275
private String collection;
7376
private final StandardEvaluationContext context;
7477

78+
private ArangoPersistentProperty arangoIdProperty;
7579
private ArangoPersistentProperty revProperty;
7680
private final Collection<ArangoPersistentProperty> hashIndexedProperties;
7781
private final Collection<ArangoPersistentProperty> skiplistIndexedProperties;
@@ -184,6 +188,9 @@ public void setApplicationContext(final ApplicationContext applicationContext) t
184188
@Override
185189
public void addPersistentProperty(final ArangoPersistentProperty property) {
186190
super.addPersistentProperty(property);
191+
if (property.isArangoIdProperty()) {
192+
arangoIdProperty = property;
193+
}
187194
if (property.isRevProperty()) {
188195
revProperty = property;
189196
}
@@ -194,6 +201,11 @@ public void addPersistentProperty(final ArangoPersistentProperty property) {
194201
property.getFulltextIndexed().ifPresent(i -> fulltextIndexedProperties.add(property));
195202
}
196203

204+
@Override
205+
public Optional<ArangoPersistentProperty> getArangoIdProperty() {
206+
return Optional.ofNullable(arangoIdProperty);
207+
}
208+
197209
@Override
198210
public Optional<ArangoPersistentProperty> getRevProperty() {
199211
return Optional.ofNullable(revProperty);
@@ -278,4 +290,24 @@ public <A extends Annotation> Set<A> findAnnotations(final Class<A> annotationTy
278290
return (Set<A>) repeatableAnnotationCache.computeIfAbsent(annotationType,
279291
it -> AnnotatedElementUtils.findMergedRepeatableAnnotations(getType(), it));
280292
}
293+
294+
private static class AbsentAccessor extends TargetAwareIdentifierAccessor {
295+
296+
public AbsentAccessor(final Object target) {
297+
super(target);
298+
}
299+
300+
@Override
301+
@Nullable
302+
public Object getIdentifier() {
303+
return null;
304+
}
305+
}
306+
307+
@Override
308+
public IdentifierAccessor getArangoIdAccessor(final Object bean) {
309+
return getArangoIdProperty().isPresent() ? new ArangoIdPropertyIdentifierAccessor(this, bean)
310+
: new AbsentAccessor(bean);
311+
}
312+
281313
}

src/main/java/com/arangodb/springframework/core/mapping/DefaultArangoPersistentProperty.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.data.mapping.model.SimpleTypeHolder;
3232
import org.springframework.util.StringUtils;
3333

34+
import com.arangodb.springframework.annotation.ArangoId;
3435
import com.arangodb.springframework.annotation.Field;
3536
import com.arangodb.springframework.annotation.From;
3637
import com.arangodb.springframework.annotation.FulltextIndexed;
@@ -65,6 +66,11 @@ protected Association<ArangoPersistentProperty> createAssociation() {
6566
return new Association<>(this, null);
6667
}
6768

69+
@Override
70+
public boolean isArangoIdProperty() {
71+
return findAnnotation(ArangoId.class) != null;
72+
}
73+
6874
@Override
6975
public boolean isRevProperty() {
7076
return findAnnotation(Rev.class) != null;
@@ -93,7 +99,9 @@ public Optional<To> getTo() {
9399
@Override
94100
public String getFieldName() {
95101
final String fieldName;
96-
if (isIdProperty()) {
102+
if (isArangoIdProperty()) {
103+
fieldName = "_id";
104+
} else if (isIdProperty()) {
97105
fieldName = "_key";
98106
} else if (isRevProperty()) {
99107
fieldName = "_rev";

src/main/java/com/arangodb/springframework/core/template/ArangoTemplate.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,12 +557,23 @@ public DocumentEntity insert(final String collectionName, final Object value) th
557557
return insert(collectionName, value, new DocumentCreateOptions());
558558
}
559559

560+
private Object getDocumentKey(final ArangoPersistentEntity<?> entity, final Object value) {
561+
Object id = entity.getIdentifierAccessor(value).getIdentifier();
562+
if (id == null) {
563+
final Object docId = entity.getArangoIdAccessor(value).getIdentifier();
564+
if (docId != null) {
565+
id = MetadataUtils.determineDocumentKeyFromId((String) docId);
566+
}
567+
}
568+
return id;
569+
}
570+
560571
@Override
561572
public <T> void upsert(final T value, final UpsertStrategy strategy) throws DataAccessException {
562573
final Class<? extends Object> entityClass = value.getClass();
563574
final ArangoPersistentEntity<?> entity = getConverter().getMappingContext().getPersistentEntity(entityClass);
564575

565-
final Object id = entity.getIdentifierAccessor(value).getIdentifier();
576+
final Object id = getDocumentKey(entity, value);
566577
if (id != null && (!(value instanceof Persistable) || !Persistable.class.cast(value).isNew())) {
567578
switch (strategy) {
568579
case UPDATE:
@@ -591,7 +602,7 @@ public <T> void upsert(final Iterable<T> value, final UpsertStrategy strategy) t
591602
final Collection<T> withId = new ArrayList<>();
592603
final Collection<T> withoutId = new ArrayList<>();
593604
for (final T e : value) {
594-
final Object id = entity.getIdentifierAccessor(e).getIdentifier();
605+
final Object id = getDocumentKey(entity, e);
595606
if (id != null && (!(e instanceof Persistable) || !Persistable.class.cast(e).isNew())) {
596607
withId.add(e);
597608
continue;
@@ -650,6 +661,7 @@ private void updateDBFields(final Object value, final DocumentEntity documentEnt
650661
if (idProperty != null) {
651662
accessor.setProperty(idProperty, documentEntity.getKey());
652663
}
664+
entity.getArangoIdProperty().ifPresent(arangoId -> accessor.setProperty(arangoId, documentEntity.getId()));
653665
entity.getRevProperty().ifPresent(rev -> accessor.setProperty(rev, documentEntity.getRev()));
654666
}
655667

src/test/java/com/arangodb/springframework/core/mapping/GeneralMappingTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@
4242
import com.arangodb.model.AqlQueryOptions;
4343
import com.arangodb.springframework.AbstractArangoTest;
4444
import com.arangodb.springframework.ArangoTestConfiguration;
45+
import com.arangodb.springframework.annotation.ArangoId;
4546
import com.arangodb.springframework.annotation.Document;
4647
import com.arangodb.springframework.annotation.Field;
48+
import com.arangodb.springframework.core.ArangoOperations.UpsertStrategy;
4749
import com.arangodb.springframework.core.mapping.testdata.BasicTestEntity;
4850
import com.arangodb.springframework.testdata.Actor;
4951
import com.arangodb.springframework.testdata.Movie;
@@ -374,4 +376,38 @@ public void twoTypesInSameCollection() {
374376
assertThat(findB.get().value, is("testB"));
375377
assertThat(findB.get().b, is("testB"));
376378
}
379+
380+
static class ArangoIdOnlyTestEntity {
381+
@ArangoId
382+
private String id;
383+
}
384+
385+
@SuppressWarnings("deprecation")
386+
@Test
387+
public void arangoIdOnly() {
388+
final ArangoIdOnlyTestEntity entity = new ArangoIdOnlyTestEntity();
389+
template.upsert(entity, UpsertStrategy.UPDATE);
390+
assertThat(entity.id, is(notNullValue()));
391+
final Optional<ArangoIdOnlyTestEntity> find = template.find(entity.id, ArangoIdOnlyTestEntity.class);
392+
assertThat(find.isPresent(), is(true));
393+
assertThat(find.get().id, is(entity.id));
394+
}
395+
396+
static class ArangoIdAndIdTestEntity {
397+
@ArangoId
398+
private String arangoId;
399+
@Id
400+
private String id;
401+
}
402+
403+
@Test
404+
public void arangoIdAndId() {
405+
final ArangoIdAndIdTestEntity entity = new ArangoIdAndIdTestEntity();
406+
entity.arangoId = "arangoIdAndIdTestEntity/test";
407+
template.insert(entity);
408+
assertThat(entity.arangoId, is("arangoIdAndIdTestEntity/test"));
409+
assertThat(entity.id, is("test"));
410+
assertThat(template.find(entity.id, ArangoIdAndIdTestEntity.class).isPresent(), is(true));
411+
assertThat(template.find(entity.arangoId, ArangoIdAndIdTestEntity.class).isPresent(), is(true));
412+
}
377413
}

0 commit comments

Comments
 (0)