Skip to content
This repository was archived by the owner on Aug 29, 2024. It is now read-only.

Commit 4f43dcf

Browse files
dsibiliokushagraThapar
authored andcommitted
Optimistic Lock implementation based on _etag field (#396)
* Optimistic Lock first implementation * Ignore warnings on "_etag" field name not matching Java conventions * Fix JUnit test that was checking on wrong illegal arguments * Update precondition not met message to match new version * Precondition is not met to is not met
1 parent a1f7dd3 commit 4f43dcf

File tree

11 files changed

+402
-137
lines changed

11 files changed

+402
-137
lines changed

.codacy.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
exclude_paths:
2+
- 'src/test/java/com/microsoft/azure/spring/data/cosmosdb/domain/Person.java'
3+
- 'src/test/java/com/microsoft/azure/spring/data/cosmosdb/repository/support/DocumentDbEntityInformationUnitTest.java'

HowToContribute.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ mvnw clean install
1515
```
1616

1717
## Test
18-
There're 3 profiles: `dev`, `integration-test-azure` and `integration-test-emulator`. Default profile is `dev`. Profile `integration-test-azure` will trigger integration test execution against Azure Cosmos DB. Profile `integration-test-emulator` will trigger integration test execution against [Azure Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator), you need to follow the link to setup emualator before test execution.
18+
There're 3 profiles: `dev`, `integration-test-azure` and `integration-test-emulator`. Default profile is `dev`. Profile `integration-test-azure` will trigger integration test execution against Azure Cosmos DB. Profile `integration-test-emulator` will trigger integration test execution against [Azure Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator), you need to follow the link to setup emulator before test execution.
1919

2020
- Run unit tests
2121
```bash
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for
4+
* license information.
5+
*/
6+
package com.microsoft.azure.spring.data.cosmosdb.common;
7+
8+
import java.util.Map;
9+
import java.util.concurrent.ConcurrentHashMap;
10+
import java.util.function.Function;
11+
12+
/**
13+
* Memoize function computation results
14+
* @author Domenico Sibilio
15+
*
16+
*/
17+
public class Memoizer<I, O> {
18+
19+
private final Map<I, O> cache = new ConcurrentHashMap<>();
20+
21+
private Memoizer() {}
22+
23+
public static <I, O> Function<I, O> memoize(Function<I, O> function) {
24+
return new Memoizer<I, O>().internalMemoize(function);
25+
}
26+
27+
private Function<I, O> internalMemoize(Function<I, O> function) {
28+
return input -> cache.computeIfAbsent(input, function);
29+
}
30+
31+
}

src/main/java/com/microsoft/azure/spring/data/cosmosdb/core/DocumentDbOperations.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
1212
import com.microsoft.azure.spring.data.cosmosdb.core.query.DocumentQuery;
1313
import com.microsoft.azure.spring.data.cosmosdb.repository.support.DocumentDbEntityInformation;
14+
1415
import org.springframework.data.domain.Page;
1516
import org.springframework.data.domain.Pageable;
1617

@@ -20,7 +21,7 @@ public interface DocumentDbOperations {
2021

2122
String getCollectionName(Class<?> entityClass);
2223

23-
DocumentCollection createCollectionIfNotExists(DocumentDbEntityInformation information);
24+
DocumentCollection createCollectionIfNotExists(DocumentDbEntityInformation<?, ?> information);
2425

2526
<T> List<T> findAll(Class<T> entityClass);
2627

@@ -38,7 +39,7 @@ public interface DocumentDbOperations {
3839

3940
<T> void upsert(String collectionName, T object, PartitionKey partitionKey);
4041

41-
<T> void deleteById(String collectionName, Object id, PartitionKey partitionKey);
42+
void deleteById(String collectionName, Object id, PartitionKey partitionKey);
4243

4344
void deleteAll(String collectionName, Class<?> domainClass);
4445

src/main/java/com/microsoft/azure/spring/data/cosmosdb/core/DocumentDbTemplate.java

Lines changed: 111 additions & 80 deletions
Large diffs are not rendered by default.

src/main/java/com/microsoft/azure/spring/data/cosmosdb/repository/support/DocumentDbEntityInformation.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.apache.commons.lang3.reflect.FieldUtils;
1818

1919
import org.springframework.data.annotation.Id;
20+
import org.springframework.data.annotation.Version;
2021
import org.springframework.data.repository.core.support.AbstractEntityInformation;
2122
import org.springframework.lang.NonNull;
2223
import org.springframework.util.ReflectionUtils;
@@ -31,12 +32,14 @@
3132

3233
public class DocumentDbEntityInformation<T, ID> extends AbstractEntityInformation<T, ID> {
3334

35+
private static final String ETAG = "_etag";
3436
private Field id;
3537
private Field partitionKeyField;
3638
private String collectionName;
3739
private Integer requestUnit;
3840
private Integer timeToLive;
3941
private IndexingPolicy indexingPolicy;
42+
private boolean isVersioned;
4043

4144
public DocumentDbEntityInformation(Class<T> domainClass) {
4245
super(domainClass);
@@ -53,6 +56,7 @@ public DocumentDbEntityInformation(Class<T> domainClass) {
5356
this.requestUnit = getRequestUnit(domainClass);
5457
this.timeToLive = getTimeToLive(domainClass);
5558
this.indexingPolicy = getIndexingPolicy(domainClass);
59+
this.isVersioned = getIsVersioned(domainClass);
5660
}
5761

5862
@SuppressWarnings("unchecked")
@@ -86,6 +90,10 @@ public IndexingPolicy getIndexingPolicy() {
8690
return this.indexingPolicy;
8791
}
8892

93+
public boolean isVersioned() {
94+
return isVersioned;
95+
}
96+
8997
public String getPartitionKeyFieldName() {
9098
if (partitionKeyField == null) {
9199
return null;
@@ -239,5 +247,13 @@ private Collection<ExcludedPath> getIndexingPolicyExcludePaths(Class<?> domainCl
239247

240248
return pathsCollection;
241249
}
250+
251+
private boolean getIsVersioned(Class<T> domainClass) {
252+
final Field findField = ReflectionUtils.findField(domainClass, ETAG);
253+
return findField != null
254+
&& findField.getType() == String.class
255+
&& findField.isAnnotationPresent(Version.class);
256+
}
257+
242258
}
243259

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for
4+
* license information.
5+
*/
6+
package com.microsoft.azure.spring.data.cosmosdb.common;
7+
8+
import org.junit.Before;
9+
import org.junit.Test;
10+
11+
import static org.junit.Assert.assertEquals;
12+
13+
import java.util.HashMap;
14+
import java.util.Map;
15+
import java.util.concurrent.atomic.AtomicInteger;
16+
import java.util.function.Function;
17+
import java.util.stream.IntStream;
18+
19+
/**
20+
*
21+
* @author Domenico Sibilio
22+
*
23+
*/
24+
public class MemoizerUnitTest {
25+
private static final String KEY = "key_1";
26+
private static final Map<String, AtomicInteger> countMap = new HashMap<>();
27+
private static final Function<String, Integer> memoizedFunction =
28+
Memoizer.memoize(MemoizerUnitTest::incrCount);
29+
30+
@Before
31+
public void setUp() {
32+
countMap.put(KEY, new AtomicInteger(0));
33+
}
34+
35+
@Test
36+
public void testMemoizedFunctionShouldBeCalledOnlyOnce() {
37+
IntStream
38+
.range(0, 10)
39+
.forEach(number -> memoizedFunction.apply(KEY));
40+
41+
assertEquals(1, countMap.get(KEY).get());
42+
}
43+
44+
@Test
45+
public void testDifferentMemoizersShouldNotShareTheSameCache() {
46+
IntStream
47+
.range(0, 10)
48+
.forEach(number -> Memoizer.memoize(MemoizerUnitTest::incrCount).apply(KEY));
49+
50+
assertEquals(10, countMap.get(KEY).get());
51+
}
52+
53+
private static int incrCount(String key) {
54+
return countMap.get(key).incrementAndGet();
55+
}
56+
57+
}

0 commit comments

Comments
 (0)