Skip to content

Commit 38f6fd8

Browse files
dnltskderXear
authored andcommitted
introduced RootAttribute and RootAttributeConfig
1 parent 5981d57 commit 38f6fd8

File tree

11 files changed

+445
-28
lines changed

11 files changed

+445
-28
lines changed

src/main/java/com/dasburo/spring/cache/dynamo/DefaultDynamoCacheWriter.java

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,23 @@
1616
package com.dasburo.spring.cache.dynamo;
1717

1818
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
19-
import com.amazonaws.services.dynamodbv2.model.*;
19+
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
20+
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
21+
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
22+
import com.amazonaws.services.dynamodbv2.model.DeleteItemRequest;
23+
import com.amazonaws.services.dynamodbv2.model.GetItemRequest;
24+
import com.amazonaws.services.dynamodbv2.model.GetItemResult;
25+
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
26+
import com.amazonaws.services.dynamodbv2.model.KeyType;
27+
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
28+
import com.amazonaws.services.dynamodbv2.model.PutItemRequest;
29+
import com.amazonaws.services.dynamodbv2.model.ResourceNotFoundException;
30+
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
31+
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
32+
import com.amazonaws.services.dynamodbv2.model.TimeToLiveSpecification;
33+
import com.amazonaws.services.dynamodbv2.model.UpdateTimeToLiveRequest;
2034
import com.amazonaws.services.dynamodbv2.util.TableUtils;
35+
import com.dasburo.spring.cache.dynamo.rootattribute.RootAttribute;
2136
import com.dasburo.spring.cache.dynamo.util.ByteUtils;
2237
import org.springframework.dao.PessimisticLockingFailureException;
2338
import org.springframework.lang.Nullable;
@@ -26,7 +41,12 @@
2641
import java.nio.ByteBuffer;
2742
import java.time.Duration;
2843
import java.time.Instant;
29-
import java.util.*;
44+
import java.util.ArrayList;
45+
import java.util.Collections;
46+
import java.util.HashMap;
47+
import java.util.List;
48+
import java.util.Map;
49+
import java.util.NoSuchElementException;
3050
import java.util.function.Function;
3151

3252
/**
@@ -43,11 +63,11 @@
4363
*
4464
* @author Georg Zimmermann
4565
*/
46-
class DefaultDynamoCacheWriter implements DynamoCacheWriter {
66+
public class DefaultDynamoCacheWriter implements DynamoCacheWriter {
4767

48-
private static final String ATTRIBUTE_KEY = "key";
49-
private static final String ATTRIBUTE_VALUE = "value";
50-
private static final String ATTRIBUTE_TTL = "ttl";
68+
public static final String ATTRIBUTE_KEY = "key";
69+
public static final String ATTRIBUTE_VALUE = "value";
70+
public static final String ATTRIBUTE_TTL = "ttl";
5171

5272
private final AmazonDynamoDB dynamoTemplate;
5373
private final Duration sleepTime;
@@ -78,12 +98,12 @@ public AmazonDynamoDB getNativeCacheWriter() {
7898
}
7999

80100
@Override
81-
public void put(String name, String key, byte[] value, @Nullable Duration ttl) {
101+
public void put(String name, String key, byte[] value, @Nullable Duration ttl, @Nullable List<RootAttribute> rootAttributes) {
82102
Assert.notNull(name, "Name must not be null!");
83103
Assert.notNull(key, "Key must not be null!");
84104

85105
execute(name, connection -> {
86-
putInternal(name, key, value, ttl);
106+
putInternal(name, key, value, ttl, rootAttributes);
87107

88108
return "OK";
89109
});
@@ -98,7 +118,7 @@ public byte[] get(String name, String key) {
98118
}
99119

100120
@Override
101-
public byte[] putIfAbsent(String name, String key, @Nullable byte[] value, @Nullable Duration ttl) {
121+
public byte[] putIfAbsent(String name, String key, @Nullable byte[] value, @Nullable Duration ttl, @Nullable List<RootAttribute> rootAttributes) {
102122
Assert.notNull(name, "Name must not be null!");
103123
Assert.notNull(key, "Key must not be null!");
104124

@@ -111,7 +131,7 @@ public byte[] putIfAbsent(String name, String key, @Nullable byte[] value, @Null
111131
try {
112132
return getInternal(name, key);
113133
} catch (NoSuchElementException e) {
114-
putInternal(name, key, value, ttl);
134+
putInternal(name, key, value, ttl, rootAttributes);
115135
} finally {
116136
if (isLockingCacheWriter()) {
117137
doUnlock(name);
@@ -205,7 +225,7 @@ private byte[] getAttributeValue(GetItemResult result) {
205225
}
206226
}
207227

208-
private void putInternal(String name, String key, @Nullable byte[] value, @Nullable Duration ttl) {
228+
private void putInternal(String name, String key, @Nullable byte[] value, @Nullable Duration ttl, @Nullable List<RootAttribute> rootAttributes) {
209229
Map<String, AttributeValue> attributeValues = new HashMap<>();
210230
attributeValues.put(ATTRIBUTE_KEY, new AttributeValue().withS(key));
211231

@@ -219,6 +239,12 @@ private void putInternal(String name, String key, @Nullable byte[] value, @Nulla
219239
attributeValues.put(ATTRIBUTE_TTL, new AttributeValue().withN(String.valueOf(Instant.now().plus(ttl).getEpochSecond())));
220240
}
221241

242+
if (rootAttributes != null) {
243+
rootAttributes.forEach(rootAttribute -> {
244+
attributeValues.put(rootAttribute.getName(), rootAttribute.getAttributeValue());
245+
});
246+
}
247+
222248
PutItemRequest putItemRequest = new PutItemRequest()
223249
.withTableName(name)
224250
.withItem(attributeValues);
@@ -233,7 +259,7 @@ private void removeInternal(String name, String key) {
233259

234260
private void doLock(String name) {
235261
// TODO should a ttl be provided for locking?
236-
putInternal(name, createCacheLockKey(name), "1".getBytes(), null);
262+
putInternal(name, createCacheLockKey(name), "1".getBytes(), null, null);
237263
}
238264

239265
private void doUnlock(String name) {

src/main/java/com/dasburo/spring/cache/dynamo/DynamoCache.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,24 @@
1515
*/
1616
package com.dasburo.spring.cache.dynamo;
1717

18+
import com.dasburo.spring.cache.dynamo.rootattribute.RootAttribute;
19+
import com.dasburo.spring.cache.dynamo.rootattribute.RootAttributeConfig;
20+
import com.dasburo.spring.cache.dynamo.rootattribute.RootAttributeReader;
1821
import org.slf4j.Logger;
1922
import org.slf4j.LoggerFactory;
2023
import org.springframework.cache.Cache;
2124
import org.springframework.cache.support.SimpleValueWrapper;
2225
import org.springframework.util.Assert;
2326

2427
import java.time.Duration;
28+
import java.util.List;
2529
import java.util.NoSuchElementException;
30+
import java.util.Objects;
2631
import java.util.concurrent.Callable;
32+
import java.util.stream.Collectors;
2733

2834
/**
29-
* Spring {@link org.springframework.cache.Cache} adapter implementation
35+
* Spring {@link Cache} adapter implementation
3036
* on top of Amazons DynamoDB.
3137
*
3238
* @author Georg Zimmermann
@@ -39,6 +45,8 @@ public class DynamoCache implements Cache {
3945
private final DynamoCacheWriter writer;
4046
private final DynamoCacheConfiguration cacheConfig;
4147

48+
private RootAttributeReader rootAttributeReader = new RootAttributeReader();
49+
4250
/**
4351
* Constructor.
4452
*
@@ -152,14 +160,14 @@ public final Duration getTtl() {
152160
@Override
153161
public void put(Object key, Object value) {
154162
Assert.isTrue(key instanceof String, "'key' must be an instance of 'java.lang.String'.");
155-
writer.put(cacheName, (String) key, serialize(value), cacheConfig.getTtl());
163+
writer.put(cacheName, (String) key, serialize(value), cacheConfig.getTtl(), readRootAttributes(cacheConfig.getRootAttributeConfigs(), value));
156164
}
157165

158166
@Override
159167
public ValueWrapper putIfAbsent(Object key, Object value) {
160168
Assert.isTrue(key instanceof String, "'key' must be an instance of 'java.lang.String'.");
161169

162-
byte[] result = writer.putIfAbsent(cacheName, (String) key, serialize(value), cacheConfig.getTtl());
170+
byte[] result = writer.putIfAbsent(cacheName, (String) key, serialize(value), cacheConfig.getTtl(), readRootAttributes(cacheConfig.getRootAttributeConfigs(), value));
163171
if (result != null) {
164172
LOGGER.debug(String.format("Key: %s already exists in the cache. Element will not be replaced.", key));
165173
return new SimpleValueWrapper(deserialize(result));
@@ -190,4 +198,11 @@ private Object deserialize(byte[] value) {
190198
private byte[] serialize(Object value) {
191199
return cacheConfig.getSerializer().serialize(value);
192200
}
201+
202+
private List<RootAttribute> readRootAttributes(List<RootAttributeConfig> rootAttributeConfigs, Object value) {
203+
return rootAttributeConfigs.stream()
204+
.map(rootAttributeConfig -> rootAttributeReader.readRootAttribute(rootAttributeConfig, value))
205+
.filter(Objects::nonNull)
206+
.collect(Collectors.toList());
207+
}
193208
}

src/main/java/com/dasburo/spring/cache/dynamo/DynamoCacheBuilder.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
package com.dasburo.spring.cache.dynamo;
1717

1818
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
19+
import com.dasburo.spring.cache.dynamo.rootattribute.RootAttributeConfig;
1920
import com.dasburo.spring.cache.dynamo.serializer.DynamoSerializer;
2021
import org.springframework.util.Assert;
2122

2223
import java.time.Duration;
24+
import java.util.List;
2325

2426
/**
2527
* A builder for {@link DynamoCache} instance.
@@ -124,12 +126,24 @@ public DynamoCacheBuilder withSerializer(DynamoSerializer serializer) {
124126
return this;
125127
}
126128

129+
/**
130+
* Give a {@link RootAttributeConfig} to the cache to be built.
131+
* Defaults to empty {@link List}.
132+
*
133+
* @param rootAttributeConfigs additional attributes written into the root level
134+
* @return this builder for chaining.
135+
*/
136+
public DynamoCacheBuilder withRootAttributes(List<RootAttributeConfig> rootAttributeConfigs) {
137+
this.cacheConfig.setRootAttributeConfigs(rootAttributeConfigs);
138+
return this;
139+
}
140+
127141
/**
128142
* Give a {@link DynamoCacheWriter} to the cache to be built.
129143
* Defaults to {@link DefaultDynamoCacheWriter}.
130144
*
131145
* @param writer a writer doing the actual cache operations.
132-
* @return this buzilder for chaining.
146+
* @return this builder for chaining.
133147
*/
134148
public DynamoCacheBuilder withWriter(DynamoCacheWriter writer) {
135149
this.writer = writer;

src/main/java/com/dasburo/spring/cache/dynamo/DynamoCacheConfiguration.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@
1414
*/
1515
package com.dasburo.spring.cache.dynamo;
1616

17+
import com.dasburo.spring.cache.dynamo.rootattribute.RootAttributeConfig;
1718
import com.dasburo.spring.cache.dynamo.serializer.DynamoSerializer;
1819
import com.dasburo.spring.cache.dynamo.serializer.StringSerializer;
1920

2021
import java.time.Duration;
22+
import java.util.List;
23+
24+
import static java.util.Collections.emptyList;
2125

2226
/**
2327
* A configuration container for a {@link DynamoCache} instance.
@@ -31,17 +35,19 @@ public class DynamoCacheConfiguration {
3135
private Long readCapacityUnits;
3236
private Long writeCapacityUnits;
3337
private DynamoSerializer serializer;
38+
private List<RootAttributeConfig> rootAttributeConfigs;
3439

35-
private DynamoCacheConfiguration(Duration ttl, boolean flushOnBoot, Long readCapacityUnits, Long writeCapacityUnits, DynamoSerializer serializer) {
40+
private DynamoCacheConfiguration(Duration ttl, boolean flushOnBoot, Long readCapacityUnits, Long writeCapacityUnits, DynamoSerializer serializer, List<RootAttributeConfig> rootAttributeConfigs) {
3641
this.ttl = ttl;
3742
this.flushOnBoot = flushOnBoot;
3843
this.readCapacityUnits = readCapacityUnits;
3944
this.writeCapacityUnits = writeCapacityUnits;
4045
this.serializer = serializer;
46+
this.rootAttributeConfigs = rootAttributeConfigs;
4147
}
4248

4349
public static DynamoCacheConfiguration defaultCacheConfig() {
44-
return new DynamoCacheConfiguration(Duration.ZERO, false, 1L, 1L, new StringSerializer());
50+
return new DynamoCacheConfiguration(Duration.ZERO, false, 1L, 1L, new StringSerializer(), emptyList());
4551
}
4652

4753
public Duration getTtl() {
@@ -83,4 +89,12 @@ public DynamoSerializer getSerializer() {
8389
public void setSerializer(DynamoSerializer serializer) {
8490
this.serializer = serializer;
8591
}
92+
93+
public List<RootAttributeConfig> getRootAttributeConfigs() {
94+
return rootAttributeConfigs;
95+
}
96+
97+
public void setRootAttributeConfigs(List<RootAttributeConfig> rootAttributeConfigs) {
98+
this.rootAttributeConfigs = rootAttributeConfigs;
99+
}
86100
}

src/main/java/com/dasburo/spring/cache/dynamo/DynamoCacheWriter.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
package com.dasburo.spring.cache.dynamo;
1717

1818
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
19+
import com.dasburo.spring.cache.dynamo.rootattribute.RootAttribute;
1920
import org.springframework.lang.Nullable;
2021
import org.springframework.util.Assert;
2122

2223
import java.time.Duration;
24+
import java.util.List;
2325

2426
/**
2527
* {@link DynamoCacheWriter} provides low level access to DynamoDB commands ({@code PUT, GET, ...}) used for
@@ -79,12 +81,13 @@ static DynamoCacheWriter lockingDynamoCacheWriter(AmazonDynamoDB dynamoTemplate)
7981
* <br><b>Note:</b> The values size must be less than 400 KB.
8082
* As this is the maximum element size of a binary in Amazons DynamoDB.
8183
*
82-
* @param name The cache name must not be {@literal null}.
83-
* @param key The key for the cache entry. Must not be {@literal null}.
84-
* @param value The value stored for the key. Must not be {@literal null}.
85-
* @param ttl Optional expiration time. Can be {@literal null}.
84+
* @param name The cache name must not be {@literal null}.
85+
* @param key The key for the cache entry. Must not be {@literal null}.
86+
* @param value The value stored for the key. Must not be {@literal null}.
87+
* @param ttl Optional expiration time. Can be {@literal null}.
88+
* @param rootAttributes Optional additional root attributes. Can be {@literal null}.
8689
*/
87-
void put(String name, String key, byte[] value, @Nullable Duration ttl);
90+
void put(String name, String key, byte[] value, @Nullable Duration ttl, @Nullable List<RootAttribute> rootAttributes);
8891

8992
/**
9093
* Get the binary value representation from Dynamo stored for the given key.
@@ -101,14 +104,15 @@ static DynamoCacheWriter lockingDynamoCacheWriter(AmazonDynamoDB dynamoTemplate)
101104
* <br><b>Note:</b> The values size must be less than 400 KB.
102105
* As this is the maximum element size of a binary in Amazons DynamoDB.
103106
*
104-
* @param name The cache name must not be {@literal null}.
105-
* @param key The key for the cache entry. Must not be {@literal null}.
106-
* @param value The value stored for the key. Must not be {@literal null}.
107-
* @param ttl Optional expiration time. Can be {@literal null}.
107+
* @param name The cache name must not be {@literal null}.
108+
* @param key The key for the cache entry. Must not be {@literal null}.
109+
* @param value The value stored for the key. Must not be {@literal null}.
110+
* @param ttl Optional expiration time. Can be {@literal null}.
111+
* @param rootAttributes Optional additional root attributes. Can be {@literal null}.
108112
* @return {@literal null} if the value has been written, the value stored for the key if it already exists.
109113
*/
110114
@Nullable
111-
byte[] putIfAbsent(String name, String key, byte[] value, @Nullable Duration ttl);
115+
byte[] putIfAbsent(String name, String key, byte[] value, @Nullable Duration ttl, @Nullable List<RootAttribute> rootAttributes);
112116

113117
/**
114118
* Remove the given key from Dynamo.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.dasburo.spring.cache.dynamo.rootattribute;
2+
3+
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
4+
5+
public class RootAttribute {
6+
7+
private String name;
8+
private AttributeValue value;
9+
10+
public RootAttribute(String name, AttributeValue value) {
11+
this.name = name;
12+
this.value = value;
13+
}
14+
15+
public String getName() {
16+
return name;
17+
}
18+
19+
public AttributeValue getAttributeValue() {
20+
return value;
21+
}
22+
23+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.dasburo.spring.cache.dynamo.rootattribute;
2+
3+
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
4+
import org.springframework.util.Assert;
5+
6+
import static com.dasburo.spring.cache.dynamo.DefaultDynamoCacheWriter.ATTRIBUTE_KEY;
7+
import static com.dasburo.spring.cache.dynamo.DefaultDynamoCacheWriter.ATTRIBUTE_TTL;
8+
import static com.dasburo.spring.cache.dynamo.DefaultDynamoCacheWriter.ATTRIBUTE_VALUE;
9+
10+
public class RootAttributeConfig {
11+
12+
private String name;
13+
private ScalarAttributeType type;
14+
15+
public RootAttributeConfig(String name, ScalarAttributeType type) {
16+
Assert.notNull(type, "type must not be null!");
17+
Assert.notNull(name, "name must not be null!");
18+
Assert.isTrue(name.length() > 0, "name must not be empty!");
19+
Assert.isTrue(!ATTRIBUTE_KEY.equalsIgnoreCase(name), "name must not equal '" + ATTRIBUTE_KEY+"'");
20+
Assert.isTrue(!ATTRIBUTE_VALUE.equalsIgnoreCase(name), "name must not equal '" + ATTRIBUTE_VALUE+"'");
21+
Assert.isTrue(!ATTRIBUTE_TTL.equalsIgnoreCase(name), "name must not equal '" + ATTRIBUTE_TTL+"'");
22+
this.name = name;
23+
this.type = type;
24+
}
25+
26+
public String getName() {
27+
return name;
28+
}
29+
30+
public ScalarAttributeType getType() {
31+
return type;
32+
}
33+
}

0 commit comments

Comments
 (0)