Skip to content

Commit 0f4f717

Browse files
tim-aeroreugn
andauthored
Implement AerospikeGeneration annotation (#182)
- 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` * Fixed a couple of references to version instead of generation * update Javadoc for AerospikeGeneration * optimize annotation processing --------- Co-authored-by: yrizhkov <[email protected]>
1 parent b820843 commit 0f4f717

File tree

11 files changed

+981
-174
lines changed

11 files changed

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

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 @AerospikeGeneration field for optimistic concurrency control
127+
Integer generationValue = entry.getGenerationValue(object);
128+
if (generationValue != null && generationValue > 0) {
129+
writePolicy.generation = generationValue;
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: 329 additions & 169 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 @AerospikeGeneration 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 generation) {
96+
this.generation = generation;
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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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)