
com.spikeify.commands.SingleKeyUpdater Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core Show documentation
Show all versions of core Show documentation
Simple ORM for Aerospike
package com.spikeify.commands;
import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.async.IAsyncClient;
import com.aerospike.client.listener.WriteListener;
import com.aerospike.client.policy.GenerationPolicy;
import com.aerospike.client.policy.RecordExistsAction;
import com.aerospike.client.policy.WritePolicy;
import com.spikeify.*;
import com.spikeify.annotations.Namespace;
import com.spikeify.annotations.SetName;
import com.spikeify.async.AbstractPendingFuture;
import com.spikeify.async.WriteListenerFuture;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
/**
* A command chain for creating or updating a single object in database.
* This class is not intended to be instantiated by user.
*
* @param
*/
@SuppressWarnings({"unchecked", "WeakerAccess"})
public class SingleKeyUpdater {
/**
* Used internally to create a command chain. Not intended to be used by the user directly.
* Instead use {@link Spikeify#update(Key, Object)} or similar method.
*/
public SingleKeyUpdater(boolean isTx,
IAsyncClient asynClient,
RecordsCache recordsCache,
boolean create,
String defaultNamespace,
T object,
K key) {
this.isTx = isTx;
this.asynClient = asynClient;
this.recordsCache = recordsCache;
this.create = create;
this.namespace = defaultNamespace;
this.object = object;
this.mapper = MapperService.getMapper((Class) object.getClass());
this.recordExpiration = mapper.getRecordExpiration(object);
if (key.getClass().equals(Key.class)) {
this.key = (Key) key;
this.keyType = KeyType.KEY;
} else if (key.getClass().equals(Long.class)) {
this.longKey = (Long) key;
this.keyType = KeyType.LONG;
} else if (key.getClass().equals(String.class)) {
this.stringKey = (String) key;
this.keyType = KeyType.STRING;
} else {
throw new IllegalArgumentException("Error: unsupported key type. " +
"SingleKeyUpdater constructor can only ba called with K as : Key, Long or String");
}
}
private final T object;
protected KeyType keyType;
protected String namespace;
protected String setName;
protected String stringKey;
protected Long longKey;
protected Key key;
protected final IAsyncClient asynClient;
protected final RecordsCache recordsCache;
protected final boolean create;
protected WritePolicy overridePolicy;
protected final ClassMapper mapper;
private final boolean isTx;
private final Integer recordExpiration;
private boolean forceReplace = false;
/**
* Sets the Namespace. Overrides the default namespace and the namespace defined on the Class via {@link Namespace} annotation.
*
* @param namespace The namespace.
*/
public SingleKeyUpdater namespace(String namespace) {
this.namespace = namespace;
return this;
}
/**
* Sets the SetName. Overrides any SetName defined on the Class via {@link SetName} annotation.
*
* @param setName The name of the set.
*/
public SingleKeyUpdater setName(String setName) {
this.setName = setName;
return this;
}
/**
* Sets the {@link WritePolicy} to be used when creating or updating the record in the database.
* Internally the 'sendKey' property of the policy will always be set to true.
* If this method is called within .transact() method then the 'generationPolicy' property will be set to GenerationPolicy.EXPECT_GEN_EQUAL
* The 'recordExistsAction' property is set accordingly depending if this is a create or update operation
*
* @param policy The policy.
*/
public SingleKeyUpdater policy(WritePolicy policy) {
this.overridePolicy = policy;
this.overridePolicy.sendKey = true;
return this;
}
private WritePolicy getPolicy(boolean nonNullField) {
WritePolicy writePolicy = overridePolicy != null ? overridePolicy : new WritePolicy(asynClient.getWritePolicyDefault());
// must be set in order for later queries to return record keys
writePolicy.sendKey = true;
if (recordExpiration != null) {
writePolicy.expiration = recordExpiration;
}
// is version checking necessary
if (isTx) {
Integer generation = mapper.getGeneration(object);
writePolicy.generationPolicy = GenerationPolicy.EXPECT_GEN_EQUAL;
if (generation != null) {
writePolicy.generation = generation;
} else {
throw new SpikeifyError("Error: missing @Generation field in class " + object.getClass() +
". When using transact(..) you must have @Generation annotation on a field in the entity class.");
}
}
if (create) {
writePolicy.recordExistsAction = RecordExistsAction.CREATE_ONLY;
if (!nonNullField) {
throw new SpikeifyError("Error: cannot create object with no writable properties. " +
"At least one object property other then UserKey must be different from NULL.");
}
} else {
if (forceReplace) {
writePolicy.recordExistsAction = RecordExistsAction.REPLACE;
} else {
writePolicy.recordExistsAction = RecordExistsAction.UPDATE;
}
}
return writePolicy;
}
/**
* Sets updater to skip cache check for object changes. This causes that all
* object properties will be written to database. It also deletes previous saved
* properties in database and now not mapped to object.
*/
public SingleKeyUpdater forceReplace() {
this.forceReplace = true;
return this;
}
protected void collectKeys() {
// check if any Long or String keys were provided
if (stringKey != null) {
key = new Key(getNamespace(), getSetName(), stringKey);
} else if (longKey != null) {
key = new Key(getNamespace(), getSetName(), longKey);
}
}
protected String getNamespace() {
if (namespace == null) {
throw new SpikeifyError("Namespace not set.");
}
return namespace;
}
protected String getSetName() {
return setName != null ? setName : mapper.getSetName();
}
/**
* Synchronously executes a single create or update command and returns the key of the record.
*
* @return The key of the record. The type of the key returned depends on the way this class is instantiated.
* It can be: {@link Key}, Long or String.
*/
@SuppressWarnings("ConstantConditions")
public K now() {
return now(null); // invoke internal sync version
}
/**
* Internal update method that can run sync or async, depending on the provided listener being provided or null.
*
* @param writeListener
* @return
*/
private K now(WriteListener writeListener) {
collectKeys();
mapper.checkKeyType(key);
if (object == null) {
throw new SpikeifyError("Error: parameter 'object' must not be null");
}
Map props = mapper.getProperties(object);
Set changedProps = recordsCache.update(key, props, create || forceReplace);
List bins = new ArrayList<>();
boolean nonNullField = false;
for (String propName : changedProps) {
Object value = props.get(propName);
if (value == null) {
if (!forceReplace) {
bins.add(Bin.asNull(propName));
}
} else if (value instanceof List>) {
bins.add(new Bin(propName, (List) value));
nonNullField = true;
} else if (value instanceof Map, ?>) {
bins.add(new Bin(propName, (Map) value));
nonNullField = true;
} else {
bins.add(new Bin(propName, value));
nonNullField = true;
}
}
WritePolicy usePolicy = getPolicy(nonNullField);
// if we are updating an existing record and no bins are to be updated,
// then just touch the entity to update expiry timestamp
if (!create && bins.isEmpty()) {
if (recordExpiration != null) {
asynClient.touch(usePolicy, key);
}
} else {
if (writeListener == null) {
asynClient.put(usePolicy, key, bins.toArray(new Bin[bins.size()])); // sync
} else {
asynClient.put(usePolicy, writeListener, key, bins.toArray(new Bin[bins.size()])); // async
}
}
return prepareKey(key);
}
public Future async() {
WriteListenerFuture future = new WriteListenerFuture() {
@Override
public K prepareResult(Key key) {
return prepareKey(key);
}
};
now(future);
return future;
}
private K prepareKey(Key key){
switch (keyType) {
case KEY:
return (K) key;
case LONG:
return (K) (Long) key.userKey.toLong();
case STRING:
return (K) key.userKey.toString();
default:
throw new IllegalStateException("Error: unsupported key type. Must be one of: Key, Long or String"); // should not happen
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy