forked from aerospike/aerospike-client-java
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/aerospike cache improvements (aerospike#151)
* Spring Cache Aerospike improvements: 1. TTL configuration support in AerospikeCacheManager. 2. Added integration tests. 3. Code cleanup & Apache license copyrights. * add missing implementations * Replace AerospikeClient with IAerospikeClient. * Support clear() method. * Added a test for "cache should not evict" scenario. * Move AerospikeSerializingCache functionality to AerospikeCache class. * Remove unnecessary casting. * Reformat imports. * Change access levels. * Refactor: 1. Remove usage of deprecated method "addCache" in getCache. 2. Use external Aerospike cache utils instead of duplicate code. 3. Code cleanup. * add license header; add private constructor * Added documentation for AerospikeCache. * 1. Fix: GetAerospikeCache creates the cache's namespace with the given name. 2. Return Cache instead of AerospikeCache in getMissingCache. * Added tests to cover Aerospike cache clear() scenarios. * Refactor: 1. Using AerospikeCacheConfiguration to keep all cache level configuration. 2. New AerospikeCache name that matches an AerospikeCacheConfiguration (the name no longer represents an Aerospike namespace, namespace is now part of the cache configuration). 3. Added Builder pattern to the cache manager and cache configuration to reduce the number of constructors. 4. Modify Aerospike cache tests to the new design. 5. Throwable instead of Exception. * Wrap get() method with Cache.ValueRetrievalException instead of log.warn message. * In case the key already exists return the existing value. Else, write the new given key-value and return null. * Added documentation for AerospikeCache methods. * Fix: adding another second to thread.sleep when clearing cache (build via git actions sometimes fails cause clear() was not completed). * Fix: remove clear cache test since clearing cache is async method and nothing guarantees that it will finish before the thread sleep duration finishes which causes git actions build to fail. * Fix: call AerospikeOperations count() method with set name only (no need to provide the class). * Avoid passing nulls in constructors. * 1. Remove Builder pattern from AerospikeCacheManager, add the missing relevant constructors. 2. Avoid passing nulls to constructors. 3. Code cleanup - modify docs and variable names. * Refactor: change configuration structure (providing a namespace is mandatory). default set is null (write directly to the namespace). default expiration is 0 (Aerospike Server's default). * 1. Changing "cache with TTL" test to use a configured cache instead of another cache manager. 2. Adding another test to cover two cache managers scenario. * Change set and expirationInSeconds to final. Co-authored-by: yrizhkov <[email protected]> Co-authored-by: Eugene R <[email protected]>
- Loading branch information
1 parent
82e2ff5
commit 796ac59
Showing
7 changed files
with
413 additions
and
209 deletions.
There are no files selected for viewing
180 changes: 129 additions & 51 deletions
180
src/main/java/org/springframework/data/aerospike/cache/AerospikeCache.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,114 +1,192 @@ | ||
/* | ||
* Copyright 2016 the original author or authors. | ||
* Copyright 2019 the original author or authors. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.springframework.data.aerospike.cache; | ||
|
||
import java.util.concurrent.Callable; | ||
|
||
import org.springframework.cache.Cache; | ||
import org.springframework.cache.support.SimpleValueWrapper; | ||
package org.springframework.data.aerospike.cache; | ||
|
||
import com.aerospike.client.AerospikeClient; | ||
import com.aerospike.client.Bin; | ||
import com.aerospike.client.IAerospikeClient; | ||
import com.aerospike.client.Key; | ||
import com.aerospike.client.Operation; | ||
import com.aerospike.client.Record; | ||
import com.aerospike.client.policy.RecordExistsAction; | ||
import com.aerospike.client.policy.WritePolicy; | ||
import org.springframework.cache.Cache; | ||
import org.springframework.cache.support.SimpleValueWrapper; | ||
import org.springframework.data.aerospike.convert.AerospikeConverter; | ||
import org.springframework.data.aerospike.convert.AerospikeReadData; | ||
import org.springframework.data.aerospike.convert.AerospikeWriteData; | ||
|
||
import java.util.Objects; | ||
import java.util.concurrent.Callable; | ||
|
||
/** | ||
* | ||
* A Cache {@link org.springframework.cache.Cache} implementation backed by Aerospike database as store. | ||
* Create and configure Aerospike cache instances via {@link AerospikeCacheManager}. | ||
* | ||
* @author Venil Noronha | ||
*/ | ||
//TODO: extract this to the separate repository aerospike-spring-cache | ||
public class AerospikeCache implements Cache { | ||
|
||
private static final String VALUE = "value"; | ||
|
||
protected AerospikeClient client; | ||
protected String namespace; | ||
protected String set; | ||
protected WritePolicy createOnly; | ||
|
||
public AerospikeCache(String namespace, String set, AerospikeClient client, | ||
long expiration){ | ||
private final String name; | ||
private final IAerospikeClient client; | ||
private final AerospikeConverter aerospikeConverter; | ||
private final AerospikeCacheConfiguration cacheConfiguration; | ||
private final WritePolicy createOnly; | ||
private final WritePolicy writePolicyForPut; | ||
|
||
public AerospikeCache(String name, | ||
IAerospikeClient client, | ||
AerospikeConverter aerospikeConverter, | ||
AerospikeCacheConfiguration cacheConfiguration) { | ||
this.name = name; | ||
this.client = client; | ||
this.namespace = namespace; | ||
this.set = set; | ||
this.createOnly = new WritePolicy(client.writePolicyDefault); | ||
this.aerospikeConverter = aerospikeConverter; | ||
this.cacheConfiguration = cacheConfiguration; | ||
this.createOnly = new WritePolicy(client.getWritePolicyDefault()); | ||
this.createOnly.recordExistsAction = RecordExistsAction.CREATE_ONLY; | ||
this.createOnly.expiration = cacheConfiguration.getExpirationInSeconds(); | ||
this.writePolicyForPut = new WritePolicy(client.getWritePolicyDefault()); | ||
this.writePolicyForPut.expiration = cacheConfiguration.getExpirationInSeconds(); | ||
} | ||
|
||
protected Key getKey(Object key){ | ||
return new Key(namespace, set, key.toString()); | ||
} | ||
|
||
private ValueWrapper toWrapper(Record record) { | ||
return (record != null ? new SimpleValueWrapper(record.getValue(VALUE)) : null); | ||
} | ||
|
||
/** | ||
* Clears the cache by truncating the configured cache's set (in the configured namespace). | ||
*/ | ||
@Override | ||
public void clear() { | ||
// TODO Auto-generated method stub | ||
client.truncate(null, cacheConfiguration.getNamespace(), cacheConfiguration.getSet(), null); | ||
} | ||
|
||
/** | ||
* Deletes the key from Aerospike database. | ||
* @param key The key to delete. | ||
*/ | ||
@Override | ||
public void evict(Object key) { | ||
this.client.delete(null, getKey(key)); | ||
|
||
client.delete(null, getKey(key)); | ||
} | ||
|
||
/** | ||
* Get cache's name. | ||
* @return The cache's name. | ||
*/ | ||
@Override | ||
public ValueWrapper get(Object key) { | ||
Record record = client.get(null, getKey(key)); | ||
ValueWrapper vr = toWrapper(record); | ||
return vr; | ||
public String getName() { | ||
return name; | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
/** | ||
* Get the underlying native cache provider - the Aerospike client. | ||
* @return The aerospike client. | ||
*/ | ||
@Override | ||
public <T> T get(Object key, Class<T> type) { | ||
return (T) client.get(null, getKey(key)); | ||
public Object getNativeCache() { | ||
return client; | ||
} | ||
|
||
/** | ||
* Return the value (bins) from the Aerospike database to which this cache maps the specified key, obtaining that value from valueLoader if necessary. | ||
* This method provides a simple substitute for the conventional "if cached, return; otherwise create, cache and return" pattern. | ||
* @param key The key whose associated value is to be returned. | ||
* @param valueLoader The value loader that might contain the value (bins). | ||
* @return The value (bins) to which this cache maps the specified key. | ||
*/ | ||
@Override | ||
public String getName() { | ||
return this.namespace+":"+this.set; | ||
@SuppressWarnings("unchecked") | ||
public <T> T get(Object key, Callable<T> valueLoader) { | ||
T value = (T) client.get(null, getKey(key)).getValue(VALUE); | ||
if (Objects.isNull(value)) { | ||
try { | ||
value = valueLoader.call(); | ||
if (Objects.nonNull(value)) { | ||
put(key, value); | ||
} | ||
} catch (Throwable e) { | ||
throw new Cache.ValueRetrievalException(key, valueLoader, e); | ||
} | ||
} | ||
return value; | ||
} | ||
|
||
/** | ||
* Return the value (bins) from the Aerospike database to which this cache maps the specified key. | ||
* Generically specifying a type that return value will be cast to. | ||
* @param key The key whose associated value (bins) is to be returned. | ||
* @param type The required type of the returned value (may be null to bypass a type check; in case of a null value found in the cache, the specified type is irrelevant). | ||
* @return The value (bins) to which this cache maps the specified key (which may be null itself), or also null if the cache contains no mapping for this key. | ||
*/ | ||
@Override | ||
public Object getNativeCache() { | ||
return client; | ||
public <T> T get(Object key, Class<T> type) { | ||
Key dbKey = getKey(key); | ||
Record record = client.get(null, dbKey); | ||
if (record != null) { | ||
AerospikeReadData data = AerospikeReadData.forRead(dbKey, record); | ||
return aerospikeConverter.read(type, data); | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Returns the value (bins) from the Aerospike database to which this cache maps the specified key. | ||
* Returns null if the cache contains no mapping for this key; otherwise, the cached value (which may be null itself) will be returned in a Cache.ValueWrapper. | ||
* @param key The key whose associated value (bins) is to be returned. | ||
* @return The value (bins) to which this cache maps the specified key, contained within a Cache.ValueWrapper which may also hold a cached null value. | ||
* A straight null being returned means that the cache contains no mapping for this key. | ||
*/ | ||
@Override | ||
public void put(Object key, Object value) { | ||
client.put(null, getKey(key), new Bin(VALUE, value)); | ||
public ValueWrapper get(Object key) { | ||
Object value = get(key, Object.class); | ||
return (value != null ? new SimpleValueWrapper(value) : null); | ||
} | ||
|
||
/** | ||
* Write the key-value pair to Aerospike database. | ||
* @param key The key to write. | ||
* @param value The value to write. | ||
*/ | ||
@Override | ||
public ValueWrapper putIfAbsent(Object key, Object value) { | ||
Record record = client.operate(this.createOnly, getKey(key), Operation.put(new Bin(VALUE, value)), Operation.get(VALUE)); | ||
return toWrapper(record); | ||
public void put(Object key, Object value) { | ||
serializeAndPut(writePolicyForPut, key, value); | ||
} | ||
|
||
/** | ||
* Write the key-value pair to Aerospike database if the key doesn't already exists. | ||
* @param key The key to write. | ||
* @param value The value (bins) to write. | ||
* @return In case the key already exists return the existing value, else return null. | ||
*/ | ||
@Override | ||
public <T> T get(Object key, Callable<T> valueLoader) { | ||
// TODO Auto-generated method stub | ||
public ValueWrapper putIfAbsent(Object key, Object value) { | ||
ValueWrapper valueWrapper = get(key); | ||
// Key already exists, return the existing value | ||
if (valueWrapper != null) { | ||
return valueWrapper; | ||
} | ||
// Key doesn't exists, write the new given key-value to Aerospike database and return null | ||
serializeAndPut(createOnly, key, value); | ||
return null; | ||
} | ||
|
||
private Key getKey(Object key){ | ||
return new Key(cacheConfiguration.getNamespace(), cacheConfiguration.getSet(), key.toString()); | ||
} | ||
|
||
private void serializeAndPut(WritePolicy writePolicy, Object key, Object value) { | ||
AerospikeWriteData data = AerospikeWriteData.forWrite(); | ||
aerospikeConverter.write(value, data); | ||
client.put(writePolicy, getKey(key), data.getBinsAsArray()); | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
src/main/java/org/springframework/data/aerospike/cache/AerospikeCacheConfiguration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package org.springframework.data.aerospike.cache; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
/** | ||
* Default set is null meaning write directly to the namespace. | ||
* Default expiration is 0 meaning use the server's default namespace configuration variable "default-ttl". | ||
*/ | ||
@Getter | ||
@AllArgsConstructor | ||
public class AerospikeCacheConfiguration { | ||
private final String namespace; | ||
private final String set; | ||
private final int expirationInSeconds; | ||
|
||
public AerospikeCacheConfiguration (String namespace) { | ||
this(namespace, null, 0); | ||
} | ||
|
||
public AerospikeCacheConfiguration (String namespace, String set) { | ||
this(namespace, set, 0); | ||
} | ||
|
||
public AerospikeCacheConfiguration (String namespace, int expirationInSeconds) { | ||
this(namespace, null, expirationInSeconds); | ||
} | ||
} |
Oops, something went wrong.