Skip to content

Commit d9f4c33

Browse files
committed
Implemented @AerospikeGeneration (#181)
- Added new annotation which can only on a field or property of type Integer or int - Only one such field can exist per class - On read, mapped the generation value back to the field - On write, if the field is set > 0, set the writePolicy.generation to this value and writePolicy.generationPolicy to EXPECT_GEN_EQUAL - Added appropriate configuration file - Added code config: `.withFieldNamed("generation").asGenerationField()` - Created unit tests for validation - Updated the AeroMapperBaseTest class to honor `test.host` if set as a system property instead of the default `localhost:3000`
1 parent 195c718 commit d9f4c33

File tree

11 files changed

+975
-167
lines changed

11 files changed

+975
-167
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.aerospike.mapper.annotations;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Mark a field or property to be used for optimistic concurrency control using Aerospike's generation field.
10+
* <p/>
11+
* The field or property must be of Integer or int type. When reading an object which has a field marked
12+
* with @Version, the returned record's generation field will be mapped into the @Version field.
13+
* When writing the record, if the @Version field is non-zero, the generation will be set in the
14+
* writePolicy.generation field and the writePolicy.generationPolicy will be set to
15+
* GenerationPolicy.EXPECT_GEN_EQUAL.
16+
* <p/>
17+
* Example usage:
18+
* <pre>
19+
* &#64;AerospikeRecord(namespace = "test", set = "account")
20+
* public class Account {
21+
* &#64;AerospikeKey
22+
* private int id;
23+
* &#64;AerospikeBin
24+
* private String name;
25+
* &#64;Version
26+
* private int version;
27+
* }
28+
* </pre>
29+
*
30+
* @author timfaulkes
31+
*/
32+
@Retention(RetentionPolicy.RUNTIME)
33+
@Target({ElementType.FIELD, ElementType.METHOD})
34+
public @interface AerospikeGeneration {
35+
}

src/main/java/com/aerospike/mapper/tools/AeroMapper.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.aerospike.client.Record;
1010
import com.aerospike.client.Value;
1111
import com.aerospike.client.policy.BatchPolicy;
12+
import com.aerospike.client.policy.GenerationPolicy;
1213
import com.aerospike.client.policy.Policy;
1314
import com.aerospike.client.policy.QueryPolicy;
1415
import com.aerospike.client.policy.RecordExistsAction;
@@ -121,6 +122,14 @@ private <T> WritePolicy generateWritePolicyFromObject(T object) {
121122
if (sendKey != null) {
122123
writePolicy.sendKey = sendKey;
123124
}
125+
126+
// #181 Handle @Version field for optimistic concurrency control
127+
Integer versionValue = entry.getGenerationValue(object);
128+
if (versionValue != null && versionValue > 0) {
129+
writePolicy.generation = versionValue;
130+
writePolicy.generationPolicy = GenerationPolicy.EXPECT_GEN_EQUAL;
131+
}
132+
124133
return writePolicy;
125134
}
126135

src/main/java/com/aerospike/mapper/tools/ClassCacheEntry.java

Lines changed: 320 additions & 156 deletions
Large diffs are not rendered by default.

src/main/java/com/aerospike/mapper/tools/ReactiveAeroMapper.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.aerospike.client.Operation;
77
import com.aerospike.client.Value;
88
import com.aerospike.client.policy.BatchPolicy;
9+
import com.aerospike.client.policy.GenerationPolicy;
910
import com.aerospike.client.policy.Policy;
1011
import com.aerospike.client.policy.QueryPolicy;
1112
import com.aerospike.client.policy.RecordExistsAction;
@@ -107,6 +108,14 @@ private <T> WritePolicy generateWritePolicyFromObject(T object) {
107108
if (sendKey != null) {
108109
writePolicy.sendKey = sendKey;
109110
}
111+
112+
// #181 Handle @Version field for optimistic concurrency control
113+
Integer generationValue = entry.getGenerationValue(object);
114+
if (generationValue != null && generationValue > 0) {
115+
writePolicy.generation = generationValue;
116+
writePolicy.generationPolicy = GenerationPolicy.EXPECT_GEN_EQUAL;
117+
}
118+
110119
return writePolicy;
111120
}
112121

src/main/java/com/aerospike/mapper/tools/configuration/BinConfig.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class BinConfig {
1414
private Integer ordinal;
1515
private EmbedConfig embed;
1616
private ReferenceConfig reference;
17+
private Boolean generation;
1718

1819
public String getName() {
1920
return name;
@@ -51,7 +52,10 @@ public ReferenceConfig getReference() {
5152
return reference;
5253
}
5354

54-
55+
public Boolean isGeneration() {
56+
return generation;
57+
}
58+
5559
public void setName(String name) {
5660
this.name = name;
5761
}
@@ -88,6 +92,10 @@ public void setReference(ReferenceConfig reference) {
8892
this.reference = reference;
8993
}
9094

95+
public void setGeneration(Boolean version) {
96+
this.generation = version;
97+
}
98+
9199
public void validate(String className) {
92100
if (StringUtils.isBlank(this.name) && StringUtils.isBlank(this.field)) {
93101
throw new AerospikeException("Configuration for class " + className + " defines a bin which contains neither a name nor a field");
@@ -129,6 +137,9 @@ public BinConfig merge(BinConfig other) {
129137
if (this.reference == null && other.reference != null) {
130138
this.reference = other.reference;
131139
}
140+
if (this.generation == null && other.generation != null) {
141+
this.generation = other.generation;
142+
}
132143
return this;
133144
}
134145
}

src/main/java/com/aerospike/mapper/tools/configuration/ClassConfig.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class ClassConfig {
1717
private String namespace;
1818
private String set;
1919
private Integer ttl;
20-
private Integer version;
20+
private Integer generation;
2121
private Boolean sendKey;
2222
private Boolean mapAll;
2323
private Boolean durableDelete;
@@ -48,7 +48,7 @@ public Integer getTtl() {
4848
}
4949

5050
public Integer getVersion() {
51-
return version;
51+
return generation;
5252
}
5353

5454
public Boolean getSendKey() {
@@ -140,8 +140,8 @@ private void setTtl(Integer ttl) {
140140
this.ttl = ttl;
141141
}
142142

143-
private void setVersion(Integer version) {
144-
this.version = version;
143+
private void setGeneration(Integer generation) {
144+
this.generation = generation;
145145
}
146146

147147
private void setSendKey(Boolean sendKey) {
@@ -199,8 +199,8 @@ public Builder withTtl(int ttl) {
199199
return this;
200200
}
201201

202-
public Builder withVersion(int version) {
203-
this.classConfig.setVersion(version);
202+
public Builder withGeneration(int generation) {
203+
this.classConfig.setGeneration(generation);
204204
return this;
205205
}
206206

@@ -321,6 +321,11 @@ public Builder beingEmbeddedAs(AerospikeEmbed.EmbedType type, AerospikeEmbed.Emb
321321
return this.end();
322322
}
323323

324+
public Builder asGenerationField() {
325+
this.binConfig.setGeneration(true);
326+
return this.end();
327+
}
328+
324329
public Builder beingEmbeddedAs(AerospikeEmbed.EmbedType type, AerospikeEmbed.EmbedType elementType, boolean saveKey) {
325330
EmbedConfig embedConfig = new EmbedConfig();
326331
embedConfig.setType(type);

src/test/java/com/aerospike/mapper/AeroMapperBaseTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.aerospike.client.policy.ClientPolicy;
77

88
import com.aerospike.client.AerospikeClient;
9+
import com.aerospike.client.Host;
910
import com.aerospike.client.IAerospikeClient;
1011
import com.aerospike.mapper.tools.ClassCache;
1112
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -25,7 +26,8 @@ public static void setupClass() {
2526
ClientPolicy policy = new ClientPolicy();
2627
// Set event loops to use in asynchronous commands.
2728
policy.eventLoops = new NioEventLoops(1);
28-
client = new AerospikeClient(policy, "localhost", 3000);
29+
Host[] hosts = Host.parseHosts(System.getProperty("test.host", "localhost:3000"), 3000);
30+
client = new AerospikeClient(policy, hosts);
2931
}
3032

3133
@AfterAll

0 commit comments

Comments
 (0)