/**
* Copyright 2014 Sunny Gleason and 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
*
* http://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 io.kazuki.v0.store.keyvalue;
import io.kazuki.v0.internal.availability.AvailabilityManager;
import io.kazuki.v0.internal.helper.EncodingHelper;
import io.kazuki.v0.internal.helper.IoHelper;
import io.kazuki.v0.internal.helper.JDBIHelper;
import io.kazuki.v0.internal.helper.LockManager;
import io.kazuki.v0.internal.helper.LogTranslation;
import io.kazuki.v0.internal.helper.SqlTypeHelper;
import io.kazuki.v0.internal.v2schema.compact.FieldTransform;
import io.kazuki.v0.internal.v2schema.compact.StructureTransform;
import io.kazuki.v0.store.KazukiException;
import io.kazuki.v0.store.Key;
import io.kazuki.v0.store.Version;
import io.kazuki.v0.store.keyvalue.KeyValueStoreIteratorJdbiImpl.KeyValueIterableJdbiImpl;
import io.kazuki.v0.store.lifecycle.Lifecycle;
import io.kazuki.v0.store.lifecycle.LifecycleSupportBase;
import io.kazuki.v0.store.management.ComponentDescriptor;
import io.kazuki.v0.store.management.ComponentRegistrar;
import io.kazuki.v0.store.management.KazukiComponent;
import io.kazuki.v0.store.management.impl.ComponentDescriptorImpl;
import io.kazuki.v0.store.management.impl.LateBindingComponentDescriptorImpl;
import io.kazuki.v0.store.schema.SchemaStore;
import io.kazuki.v0.store.schema.TypeValidation;
import io.kazuki.v0.store.schema.model.Schema;
import io.kazuki.v0.store.sequence.ResolvedKey;
import io.kazuki.v0.store.sequence.SequenceService;
import io.kazuki.v0.store.sequence.SequenceServiceJdbiImpl;
import io.kazuki.v0.store.sequence.VersionImpl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.sql.DataSource;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.skife.jdbi.v2.Handle;
import org.skife.jdbi.v2.IDBI;
import org.skife.jdbi.v2.Query;
import org.skife.jdbi.v2.TransactionCallback;
import org.skife.jdbi.v2.TransactionStatus;
import org.skife.jdbi.v2.Update;
import org.skife.jdbi.v2.exceptions.CallbackFailedException;
import org.slf4j.Logger;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
/**
* Abstract implementation of key-value storage based on JDBI.
*/
public abstract class KeyValueStoreJdbiBaseImpl
implements
KeyValueStore,
KeyValueStoreIteration,
KeyValueStoreRegistration {
public static int MULTIGET_MAX_KEYS = 3000;
protected final Logger log = LogTranslation.getLogger(getClass());
protected final AvailabilityManager availability;
protected final KazukiComponent<DataSource> dataSource;
protected final IDBI database;
protected final LockManager lockManager;
protected final SchemaStore schemaService;
protected final SequenceService sequences;
protected final SqlTypeHelper typeHelper;
protected final List<KeyValueStoreListener> kvListeners;
protected abstract String getPrefix();
protected final Lock nukeLock = new ReentrantLock();
protected final String tableName;
protected final ComponentDescriptor<KeyValueStore> componentDescriptor;
protected volatile Lifecycle lifecycle;
public KeyValueStoreJdbiBaseImpl(AvailabilityManager availability, LockManager lockManager,
KazukiComponent<DataSource> dataSource, IDBI database, SqlTypeHelper typeHelper,
SchemaStore schemaService, SequenceService sequences, String groupName, String storeName,
String partitionName) {
this.availability = availability;
this.lockManager = lockManager;
this.dataSource = dataSource;
this.database = database;
this.schemaService = schemaService;
this.sequences = sequences;
this.typeHelper = typeHelper;
this.kvListeners = new ArrayList<KeyValueStoreListener>();
this.tableName = "_" + groupName + "_" + storeName + "__kv__" + partitionName;
this.componentDescriptor =
new ComponentDescriptorImpl<KeyValueStore>("KZ:KeyValueStore:" + groupName + "-"
+ storeName + "-" + partitionName, KeyValueStore.class, (KeyValueStore) this,
new ImmutableList.Builder().add((new LateBindingComponentDescriptorImpl<Lifecycle>() {
@Override
public KazukiComponent<Lifecycle> get() {
return (KazukiComponent<Lifecycle>) KeyValueStoreJdbiBaseImpl.this.lifecycle;
}
}), ((KazukiComponent) this.lockManager).getComponentDescriptor(),
this.dataSource.getComponentDescriptor(),
((KazukiComponent) this.sequences).getComponentDescriptor(),
((KazukiComponent) this.schemaService).getComponentDescriptor()).build());
}
@Inject
public void register(Lifecycle lifecycle) {
this.lifecycle = lifecycle;
lifecycle.register(new LifecycleSupportBase() {
@Override
public void init() {
KeyValueStoreJdbiBaseImpl.this.initialize();
}
@Override
public void stop() {
availability.setAvailable(false);
}
});
}
@Override
public void addListener(KeyValueStoreListener listener) {
this.kvListeners.add(listener);
}
@Override
public ComponentDescriptor<KeyValueStore> getComponentDescriptor() {
return this.componentDescriptor;
}
@Override
@Inject
public void registerAsComponent(ComponentRegistrar manager) {
manager.register(this.componentDescriptor);
}
@Override
public void initialize() {
log.debug("Intitializing KeyValueStore {}", this);
database.inTransaction(new TransactionCallback<Void>() {
@Override
public Void inTransaction(Handle handle, TransactionStatus status) throws Exception {
performInitialization(handle, tableName);
return null;
}
});
availability.setAvailable(true);
log.debug("Intitialized KeyValueStore {}", this);
}
@Override
public Key parseKey(String keyString) throws KazukiException {
return sequences.parseKey(keyString);
}
@Override
public Version parseVersion(String versionString) throws KazukiException {
return sequences.parseVersion(versionString);
}
@Override
public <T> KeyValuePair<T> create(final String type, Class<T> clazz, final T inValue,
TypeValidation typeSafety) throws KazukiException {
return create(type, clazz, inValue, null, typeSafety);
}
@Override
public <T> KeyValuePair<T> create(final String type, final Class<T> clazz, final T inValue,
final ResolvedKey idOverride, TypeValidation typeSafety) throws KazukiException {
availability.assertAvailable();
if (type == null
|| (TypeValidation.STRICT.equals(typeSafety) && ((type.contains("@") || type.contains("$"))))) {
throw new IllegalArgumentException("Invalid entity 'type'");
}
try (LockManager toRelease = lockManager.acquire()) {
final Key newKey;
final ResolvedKey resolvedKey;
if (idOverride != null) {
newKey = sequences.unresolveKey(idOverride);
resolvedKey = idOverride;
} else {
newKey = sequences.nextKey(type);
resolvedKey = sequences.resolveKey(newKey);
}
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(type);
final Schema schema = (schemaKv == null) ? null : schemaKv.getValue();
final Version schemaVersion = schemaKv == null ? null : schemaKv.getVersion();
try {
return database.inTransaction(new TransactionCallback<KeyValuePair<T>>() {
@Override
public KeyValuePair<T> inTransaction(Handle handle, TransactionStatus status)
throws Exception {
Object storeValue = EncodingHelper.asJsonMap(inValue);
if (schema != null) {
FieldTransform fieldTransform = new FieldTransform(schema);
StructureTransform structureTransform = new StructureTransform(schema);
Map<String, Object> fieldTransformed =
fieldTransform.pack((Map<String, Object>) storeValue);
for (KeyValueStoreListener kvListener : kvListeners) {
kvListener.enforceUnique(type, clazz, schema, resolvedKey, fieldTransformed);
kvListener.onCreate(handle, type, clazz, schema, resolvedKey, fieldTransformed);
}
storeValue = structureTransform.pack(fieldTransformed);
}
byte[] storeValueBytes = EncodingHelper.convertToSmile(storeValue);
DateTime createdDate = new DateTime();
int inserted =
doInsert(handle, resolvedKey, (VersionImpl) schemaVersion, storeValueBytes,
createdDate);
if (inserted < 1) {
throw new KazukiException("Entity not created!");
}
return new KeyValuePair<T>(newKey, VersionImpl.createInternal(newKey, 1L),
schemaVersion, inValue);
}
});
} catch (CallbackFailedException e) {
if (e.getCause() != null || e.getCause().getCause() != null
&& e.getCause().getCause() instanceof KazukiException) {
throw (KazukiException) e.getCause().getCause();
}
throw e;
}
}
}
@Override
public <T> T retrieve(final Key realKey, final Class<T> clazz) throws KazukiException {
KeyValuePair<T> result = retrieveVersioned(realKey, clazz);
return (result == null) ? null : result.getValue();
}
@Override
public <T> KeyValuePair<T> retrieveVersioned(final Key realKey, final Class<T> clazz)
throws KazukiException {
availability.assertAvailable();
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(realKey.getTypePart());
final Schema schema = schemaKv == null ? null : schemaKv.getValue();
final Version schemaVersion = schemaKv == null ? null : schemaKv.getVersion();
final ResolvedKey resolvedKey = sequences.resolveKey(realKey);
return database.inTransaction(new TransactionCallback<KeyValuePair<T>>() {
@Override
public KeyValuePair<T> inTransaction(Handle handle, TransactionStatus status)
throws Exception {
try {
Map<String, Object> objectMap = loadObjectMap(handle, resolvedKey);
byte[] objectBytes = getObjectBytes(objectMap);
if (objectBytes == null) {
return null;
}
Version version =
VersionImpl.createInternal(realKey, ((Number) objectMap.get("_version")).longValue());
Object storedValue = EncodingHelper.parseSmile(objectBytes, Object.class);
if (schema != null && storedValue instanceof List) {
FieldTransform fieldTransform = new FieldTransform(schema);
StructureTransform structureTransform = new StructureTransform(schema);
storedValue =
fieldTransform.unpack(structureTransform.unpack((List<Object>) storedValue));
}
return new KeyValuePair<T>(realKey, version, schemaVersion, EncodingHelper.asValue(
(Map<String, Object>) storedValue, clazz));
} catch (Exception e) {
throw new KazukiException(e);
}
}
});
}
@Override
public <T> Map<Key, T> multiRetrieve(final Collection<Key> keys, final Class<T> clazz)
throws KazukiException {
availability.assertAvailable();
if (keys == null || keys.isEmpty()) {
return Collections.emptyMap();
}
Preconditions.checkArgument(keys.size() <= MULTIGET_MAX_KEYS, "Multiget max is %s keys",
MULTIGET_MAX_KEYS);
final Map<String, Schema> schemaMap = new HashMap<>(keys.size());
for (Key realKey : keys) {
String type = realKey.getTypePart();
if (schemaMap.containsKey(type)) {
continue;
}
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(type);
final Schema schema = schemaKv == null ? null : schemaKv.getValue();
schemaMap.put(type, schema);
}
return database.inTransaction(new TransactionCallback<Map<Key, T>>() {
@Override
public Map<Key, T> inTransaction(Handle handle, TransactionStatus status) throws Exception {
Map<Key, T> dbFound = new LinkedHashMap<Key, T>();
for (Key realKey : keys) {
final ResolvedKey resolvedKey = sequences.resolveKey(realKey);
Query<Map<String, Object>> select =
JDBIHelper.getBoundQuery(handle, getPrefix(), "kv_table_name", tableName,
"kv_retrieve");
select.bind("key_type", resolvedKey.getTypeTag());
select.bind("key_id_hi", resolvedKey.getIdentifierHi());
select.bind("key_id_lo", resolvedKey.getIdentifierLo());
List<Map<String, Object>> results = select.list();
if (results == null || results.isEmpty()) {
dbFound.put(realKey, null);
continue;
}
Map<String, Object> first = results.iterator().next();
Object storedValue =
EncodingHelper.parseSmile((byte[]) first.get("_value"), Object.class);
final Schema schema = schemaMap.get(realKey.getTypePart());
if (schema != null && storedValue instanceof List) {
FieldTransform fieldTransform = new FieldTransform(schema);
StructureTransform structureTransform = new StructureTransform(schema);
storedValue =
fieldTransform.unpack(structureTransform.unpack((List<Object>) storedValue));
}
dbFound.put(realKey, EncodingHelper.asValue((Map<String, Object>) storedValue, clazz));
}
return dbFound;
}
});
}
@Override
public <T> Map<Key, KeyValuePair<T>> multiRetrieveVersioned(final Collection<Key> keys,
final Class<T> clazz) throws KazukiException {
availability.assertAvailable();
if (keys == null || keys.isEmpty()) {
return Collections.emptyMap();
}
Preconditions.checkArgument(keys.size() <= MULTIGET_MAX_KEYS, "Multiget max is %s keys",
MULTIGET_MAX_KEYS);
final Map<String, KeyValuePair<Schema>> schemaMap = new HashMap<>(keys.size());
for (Key realKey : keys) {
String type = realKey.getTypePart();
if (schemaMap.containsKey(type)) {
continue;
}
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(realKey.getTypePart());
schemaMap.put(type, schemaKv);
}
return database.inTransaction(new TransactionCallback<Map<Key, KeyValuePair<T>>>() {
@Override
public Map<Key, KeyValuePair<T>> inTransaction(Handle handle, TransactionStatus status)
throws Exception {
Map<Key, KeyValuePair<T>> dbFound = new LinkedHashMap<Key, KeyValuePair<T>>();
for (Key realKey : keys) {
final ResolvedKey resolvedKey = sequences.resolveKey(realKey);
Query<Map<String, Object>> select =
JDBIHelper.getBoundQuery(handle, getPrefix(), "kv_table_name", tableName,
"kv_retrieve");
select.bind("key_type", resolvedKey.getTypeTag());
select.bind("key_id_hi", resolvedKey.getIdentifierHi());
select.bind("key_id_lo", resolvedKey.getIdentifierLo());
List<Map<String, Object>> results = select.list();
if (results == null || results.isEmpty()) {
dbFound.put(realKey, null);
continue;
}
Map<String, Object> first = results.iterator().next();
Version version =
VersionImpl.createInternal(realKey, ((Number) first.get("_version")).longValue());
Object storedValue =
EncodingHelper.parseSmile((byte[]) first.get("_value"), Object.class);
final KeyValuePair<Schema> schemaKv = schemaMap.get(realKey.getTypePart());
final Schema schema = schemaKv == null ? null : schemaKv.getValue();
final Version schemaVersion = schemaKv == null ? null : schemaKv.getVersion();
if (schema != null && storedValue instanceof List) {
FieldTransform fieldTransform = new FieldTransform(schema);
StructureTransform structureTransform = new StructureTransform(schema);
storedValue =
fieldTransform.unpack(structureTransform.unpack((List<Object>) storedValue));
}
dbFound.put(
realKey,
new KeyValuePair<T>(realKey, version, schemaVersion, EncodingHelper.asValue(
(Map<String, Object>) storedValue, clazz)));
}
return dbFound;
}
});
}
@Override
public <T> boolean update(final Key realKey, final Class<T> clazz, final T inValue)
throws KazukiException {
availability.assertAvailable();
try (LockManager toRelease = lockManager.acquire()) {
final String type = realKey.getTypePart();
final ResolvedKey resolvedKey = sequences.resolveKey(realKey);
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(type);
final Schema schema = schemaKv == null ? null : schemaKv.getValue();
try {
return database.inTransaction(new TransactionCallback<Boolean>() {
@Override
public Boolean inTransaction(Handle handle, TransactionStatus status) throws Exception {
Map<String, Object> storeValueMap = EncodingHelper.asJsonMap(inValue);
Object storeValue = storeValueMap;
FieldTransform fieldTransform = null;
StructureTransform structureTransform = null;
Map<String, Object> fieldTransformed = null;
Map<String, Object> objectMap = null;
Map<String, Object> oldInstance = null;
if (schema != null) {
fieldTransform = new FieldTransform(schema);
structureTransform = new StructureTransform(schema);
fieldTransformed = fieldTransform.pack((Map<String, Object>) storeValue);
storeValue = structureTransform.pack(fieldTransformed);
for (KeyValueStoreListener kvListener : kvListeners) {
kvListener.enforceUnique(type, clazz, schema, resolvedKey, fieldTransformed);
}
objectMap = loadObjectMap(handle, resolvedKey);
oldInstance =
structureTransform.unpack((List<Object>) EncodingHelper.parseSmile(
getObjectBytes(objectMap), Object.class));
}
int updatedCount =
doUpdate(handle, resolvedKey, (VersionImpl) schemaKv.getVersion(),
EncodingHelper.convertToSmile(storeValue));
boolean updated = (updatedCount == 1);
if (updated && schema != null) {
for (KeyValueStoreListener kvListener : kvListeners) {
kvListener.onUpdate(handle, type, clazz, schema, resolvedKey, fieldTransformed,
oldInstance);
}
}
return updatedCount == 1;
}
});
} catch (CallbackFailedException e) {
if (e.getCause() != null || e.getCause().getCause() != null
&& e.getCause().getCause() instanceof KazukiException) {
throw (KazukiException) e.getCause().getCause();
}
throw e;
}
}
}
@Override
public <T> Version updateVersioned(final Key realKey, final Version version,
final Class<T> clazz, final T inValue) throws KazukiException {
availability.assertAvailable();
try (LockManager toRelease = lockManager.acquire()) {
final String type = realKey.getTypePart();
final ResolvedKey resolvedKey = sequences.resolveKey(realKey);
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(type);
final Schema schema = schemaKv == null ? null : schemaKv.getValue();
try {
return database.inTransaction(new TransactionCallback<Version>() {
@Override
public Version inTransaction(Handle handle, TransactionStatus status) throws Exception {
Map<String, Object> storeValueMap = EncodingHelper.asJsonMap(inValue);
Object storeValue = storeValueMap;
FieldTransform fieldTransform = null;
StructureTransform structureTransform = null;
Map<String, Object> fieldTransformed = null;
Map<String, Object> objectMap = null;
Map<String, Object> oldInstance = null;
if (schema != null) {
fieldTransform = new FieldTransform(schema);
structureTransform = new StructureTransform(schema);
fieldTransformed = fieldTransform.pack((Map<String, Object>) storeValue);
storeValue = structureTransform.pack(fieldTransformed);
for (KeyValueStoreListener kvListener : kvListeners) {
kvListener.enforceUnique(type, clazz, schema, resolvedKey, fieldTransformed);
}
objectMap = loadObjectMap(handle, resolvedKey);
oldInstance =
structureTransform.unpack((List<Object>) EncodingHelper.parseSmile(
getObjectBytes(objectMap), Object.class));
}
int updatedCount =
doUpdateVersioned(handle, resolvedKey, (VersionImpl) version,
(VersionImpl) schemaKv.getVersion(), EncodingHelper.convertToSmile(storeValue));
boolean updated = (updatedCount == 1);
if (updated && schema != null) {
for (KeyValueStoreListener kvListener : kvListeners) {
kvListener.onUpdate(handle, type, clazz, schema, resolvedKey, fieldTransformed,
oldInstance);
}
}
return updated ? VersionImpl.createInternal(realKey,
((VersionImpl) version).getInternalIdentifier() + 1L) : null;
}
});
} catch (CallbackFailedException e) {
if (e.getCause() != null || e.getCause().getCause() != null
&& e.getCause().getCause() instanceof KazukiException) {
throw (KazukiException) e.getCause().getCause();
}
throw e;
}
}
}
@Override
public boolean delete(final Key realKey) throws KazukiException {
availability.assertAvailable();
try (LockManager toRelease = lockManager.acquire()) {
final ResolvedKey resolvedKey = sequences.resolveKey(realKey);
final String type = realKey.getTypePart();
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(type);
final Schema schema = schemaKv == null ? null : schemaKv.getValue();
return database.inTransaction(new TransactionCallback<Boolean>() {
@Override
public Boolean inTransaction(Handle handle, TransactionStatus status) throws Exception {
if (schema != null && !kvListeners.isEmpty()) {
StructureTransform structureTransform = new StructureTransform(schema);
Map<String, Object> objectMap = loadObjectMap(handle, resolvedKey);
Map<String, Object> oldInstance =
structureTransform.unpack((List<Object>) EncodingHelper.parseSmile(
getObjectBytes(objectMap), Object.class));
for (KeyValueStoreListener kvListener : kvListeners) {
kvListener.onDelete(handle, type, LinkedHashMap.class, schema, resolvedKey,
oldInstance);
}
}
Update delete =
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName,
"kv_delete");
delete.bind("updated_dt", getEpochSecondsNow());
delete.bind("key_type", resolvedKey.getTypeTag());
delete.bind("key_id_hi", resolvedKey.getIdentifierHi());
delete.bind("key_id_lo", resolvedKey.getIdentifierLo());
int deletedCount = delete.execute();
return (deletedCount == 1);
}
});
}
}
@Override
public boolean deleteVersioned(final Key realKey, final Version version) throws KazukiException {
availability.assertAvailable();
try (LockManager toRelease = lockManager.acquire()) {
final ResolvedKey resolvedKey = sequences.resolveKey(realKey);
final String type = realKey.getTypePart();
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(type);
final Schema schema = schemaKv == null ? null : schemaKv.getValue();
return database.inTransaction(new TransactionCallback<Boolean>() {
@Override
public Boolean inTransaction(Handle handle, TransactionStatus status) throws Exception {
if (schema != null && !kvListeners.isEmpty()) {
StructureTransform structureTransform = new StructureTransform(schema);
Map<String, Object> objectMap = loadObjectMap(handle, resolvedKey);
Map<String, Object> oldInstance =
structureTransform.unpack((List<Object>) EncodingHelper.parseSmile(
getObjectBytes(objectMap), Object.class));
for (KeyValueStoreListener kvListener : kvListeners) {
kvListener.onDelete(handle, type, LinkedHashMap.class, schema, resolvedKey,
oldInstance);
}
}
Update delete =
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName,
"kv_delete_versioned");
delete.bind("updated_dt", getEpochSecondsNow());
delete.bind("key_type", resolvedKey.getTypeTag());
delete.bind("key_id_hi", resolvedKey.getIdentifierHi());
delete.bind("key_id_lo", resolvedKey.getIdentifierLo());
delete.bind("old_version", ((VersionImpl) version).getInternalIdentifier());
int deletedCount = delete.execute();
return (deletedCount == 1);
}
});
}
}
@Override
public boolean deleteHard(final Key realKey) throws KazukiException {
availability.assertAvailable();
try (LockManager toRelease = lockManager.acquire()) {
final ResolvedKey resolvedKey = sequences.resolveKey(realKey);
final String type = realKey.getTypePart();
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(type);
final Schema schema = schemaKv == null ? null : schemaKv.getValue();
return database.inTransaction(new TransactionCallback<Boolean>() {
@Override
public Boolean inTransaction(Handle handle, TransactionStatus status) throws Exception {
if (schema != null && !kvListeners.isEmpty()) {
StructureTransform structureTransform = new StructureTransform(schema);
Map<String, Object> objectMap = loadObjectMap(handle, resolvedKey);
Map<String, Object> oldInstance =
structureTransform.unpack((List<Object>) EncodingHelper.parseSmile(
getObjectBytes(objectMap), Object.class));
for (KeyValueStoreListener kvListener : kvListeners) {
kvListener.onDelete(handle, type, LinkedHashMap.class, schema, resolvedKey,
oldInstance);
}
}
Update delete =
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName,
"kv_delete_hard");
delete.bind("updated_dt", getEpochSecondsNow());
delete.bind("key_type", resolvedKey.getTypeTag());
delete.bind("key_id_hi", resolvedKey.getIdentifierHi());
delete.bind("key_id_lo", resolvedKey.getIdentifierLo());
int deletedCount = delete.execute();
return (deletedCount == 1);
}
});
}
}
@Override
public boolean deleteHardVersioned(final Key realKey, final Version version)
throws KazukiException {
availability.assertAvailable();
try (LockManager toRelease = lockManager.acquire()) {
final ResolvedKey resolvedKey = sequences.resolveKey(realKey);
final String type = realKey.getTypePart();
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(type);
final Schema schema = schemaKv == null ? null : schemaKv.getValue();
return database.inTransaction(new TransactionCallback<Boolean>() {
@Override
public Boolean inTransaction(Handle handle, TransactionStatus status) throws Exception {
if (schema != null && !kvListeners.isEmpty()) {
StructureTransform structureTransform = new StructureTransform(schema);
Map<String, Object> objectMap = loadObjectMap(handle, resolvedKey);
Map<String, Object> oldInstance =
structureTransform.unpack((List<Object>) EncodingHelper.parseSmile(
getObjectBytes(objectMap), Object.class));
for (KeyValueStoreListener kvListener : kvListeners) {
kvListener.onDelete(handle, type, LinkedHashMap.class, schema, resolvedKey,
oldInstance);
}
}
Update delete =
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName,
"kv_delete_hard_versioned");
delete.bind("updated_dt", getEpochSecondsNow());
delete.bind("key_type", resolvedKey.getTypeTag());
delete.bind("key_id_hi", resolvedKey.getIdentifierHi());
delete.bind("key_id_lo", resolvedKey.getIdentifierLo());
delete.bind("old_version", ((VersionImpl) version).getInternalIdentifier());
int deletedCount = delete.execute();
return (deletedCount == 1);
}
});
}
}
@Override
public Long approximateSize(String type) throws KazukiException {
availability.assertAvailable();
Key nextId = ((SequenceServiceJdbiImpl) sequences).peekKey(type);
ResolvedKey resolvedKey = sequences.resolveKey(nextId);
return (nextId == null) ? 0L : resolvedKey.getIdentifierLo();
}
public void clear(final boolean preserveTypes, final boolean preserveCounters) {
log.debug("Clearing KeyValueStore {} table {}", this, tableName);
availability.assertAvailable();
nukeLock.lock();
try {
database.inTransaction(new TransactionCallback<Void>() {
@Override
public Void inTransaction(Handle handle, TransactionStatus status) throws Exception {
if (preserveTypes) {
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName,
"kv_reset");
} else {
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName,
"kv_truncate").execute();
performInitialization(handle, tableName);
}
return null;
}
});
sequences.clear(preserveTypes, preserveCounters);
} finally {
nukeLock.unlock();
}
log.debug("Cleared KeyValueStore {} table {}", this, tableName);
}
public void clear(final String type) throws KazukiException {
log.debug("Clearing KeyValueStore {} table {} type {}", this, tableName, type);
availability.assertAvailable();
nukeLock.lock();
final int typeId = sequences.getTypeId(type, false);
try {
database.inTransaction(new TransactionCallback<Void>() {
@Override
public Void inTransaction(Handle handle, TransactionStatus status) throws Exception {
JDBIHelper
.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName, "kv_clear_type")
.bind("key_type", typeId).execute();
return null;
}
});
log.debug("Cleared KeyValueStore {} table {} type {}", this, tableName, type);
} finally {
nukeLock.unlock();
}
}
public void destroy() {
log.debug("Destroying KeyValueStore {} table {}", this, tableName);
availability.assertAvailable();
nukeLock.lock();
try {
database.inTransaction(new TransactionCallback<Void>() {
@Override
public Void inTransaction(Handle handle, TransactionStatus status) throws Exception {
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName,
"kv_truncate").execute();
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName,
"kv_destroy");
return null;
}
});
} finally {
nukeLock.unlock();
}
log.debug("Destroyed KeyValueStore {} table {}", this, tableName);
}
@Override
public KeyValueStoreIteration iterators() {
return this;
}
@Override
public <T> KeyValueIterator<T> iterator(String type, Class<T> clazz, SortDirection sortDirection) {
return this.values(type, clazz, sortDirection).iterator();
}
@Override
public <T> KeyValueIterator<T> iterator(String type, Class<T> clazz, SortDirection sortDirection,
@Nullable Long offset, @Nullable Long limit) {
return this.values(type, clazz, sortDirection, offset, limit).iterator();
}
@Override
public <T> KeyValueIterable<KeyValuePair<T>> entries(String type, Class<T> clazz,
SortDirection sortDirection) {
return this.entries(type, clazz, sortDirection, null, null);
}
@Override
public <T> KeyValueIterable<KeyValuePair<T>> entries(final String type, final Class<T> clazz,
SortDirection sortDirection, @Nullable final Long offset, @Nullable final Long limit) {
final Handle handle = database.open();
try {
KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(type);
return new KeyValueIterableJdbiImpl<T>(availability, sequences,
KeyValueStoreJdbiBaseImpl.this, schemaKv != null ? schemaKv.getValue() : null, handle,
typeHelper.getPrefix(), "_key_id_lo", JDBIHelper.getBoundQuery(handle,
KeyValueStoreJdbiBaseImpl.this.typeHelper.getPrefix(), "kv_table_name", tableName,
"kv_key_values_of_type"), type, clazz, sortDirection, offset, limit, true, true);
} catch (KazukiException e) {
throw Throwables.propagate(e);
}
}
@Override
public <T> KeyValueIterable<Key> keys(String type, Class<T> clazz, SortDirection sortDirection) {
return this.keys(type, clazz, sortDirection, null, null);
}
@Override
public <T> KeyValueIterable<Key> keys(final String type, final Class<T> clazz,
final SortDirection sortDirection, @Nullable final Long offset, @Nullable final Long limit) {
try {
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(type);
return new KeyValueIterable<Key>() {
private final Handle handle = database.open();
private volatile KeyValueIterableJdbiImpl<T> inner = new KeyValueIterableJdbiImpl<T>(
availability, sequences, KeyValueStoreJdbiBaseImpl.this, schemaKv != null
? schemaKv.getValue()
: null, handle, typeHelper.getPrefix(), "_key_id_lo", JDBIHelper.getBoundQuery(
handle, typeHelper.getPrefix(), "kv_table_name", tableName, "kv_key_ids_of_type"),
type, clazz, sortDirection, offset, limit, false, true);
@Override
public KeyValueIterator<Key> iterator() {
return new KeyValueIterator<Key>() {
volatile KeyValueIterator<KeyValuePair<T>> innerIter = inner.iterator();
@Override
public boolean hasNext() {
if (innerIter == null) {
return false;
}
return innerIter.hasNext();
}
@Override
public Key next() {
return innerIter.next().getKey();
}
@Override
public void remove() {
innerIter.remove();
}
@Override
public void close() {
IoHelper.closeQuietly(innerIter, log);
innerIter = null;
}
};
}
@Override
public void close() {
IoHelper.closeQuietly(inner, log);
inner = null;
}
};
} catch (KazukiException e) {
throw Throwables.propagate(e);
}
}
@Override
public <T> KeyValueIterable<T> values(String type, Class<T> clazz, SortDirection sortDirection) {
return this.values(type, clazz, sortDirection, null, null);
}
@Override
public <T> KeyValueIterable<T> values(final String type, final Class<T> clazz,
final SortDirection sortDirection, @Nullable final Long offset, @Nullable final Long limit) {
try {
final KeyValuePair<Schema> schemaKv = schemaService.retrieveSchema(type);
return new KeyValueIterable<T>() {
private final Handle handle = database.open();
private volatile KeyValueIterableJdbiImpl<T> inner = new KeyValueIterableJdbiImpl<T>(
availability, sequences, KeyValueStoreJdbiBaseImpl.this, schemaKv != null
? schemaKv.getValue()
: null, handle, typeHelper.getPrefix(), "_key_id_lo", JDBIHelper.getBoundQuery(
handle, KeyValueStoreJdbiBaseImpl.this.typeHelper.getPrefix(), "kv_table_name",
tableName, "kv_key_values_of_type"), type, clazz, sortDirection, offset, limit,
true, true);
@Override
public KeyValueIterator<T> iterator() {
return new KeyValueIterator<T>() {
volatile KeyValueIterator<KeyValuePair<T>> innerIter = inner.iterator();
@Override
public boolean hasNext() {
if (innerIter == null) {
return false;
}
return innerIter.hasNext();
}
@Override
public T next() {
return innerIter.next().getValue();
}
@Override
public void remove() {
innerIter.remove();
}
@Override
public void close() {
IoHelper.closeQuietly(innerIter, log);
innerIter = null;
}
};
}
@Override
public void close() {
IoHelper.closeQuietly(inner, log);
inner = null;
}
};
} catch (KazukiException e) {
throw Throwables.propagate(e);
}
}
private byte[] getObjectBytes(Map<String, Object> objectMap) {
if (objectMap == null) {
return null;
}
return (byte[]) objectMap.get("_value");
}
private Map<String, Object> loadObjectMap(final Handle handle, final ResolvedKey key)
throws KazukiException {
Query<Map<String, Object>> select =
JDBIHelper.getBoundQuery(handle, getPrefix(), "kv_table_name", tableName, "kv_retrieve");
select.bind("key_type", key.getTypeTag());
select.bind("key_id_hi", key.getIdentifierHi());
select.bind("key_id_lo", key.getIdentifierLo());
List<Map<String, Object>> results = select.list();
if (results == null || results.isEmpty()) {
return null;
}
return results.iterator().next();
}
private void performInitialization(Handle handle, String tableName) {
log.debug("Creating table if not exist with name {} for KeyValueStore {}", tableName, this);
try {
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName,
"kv_create_table").execute();
} catch (Throwable t) {
if (!this.typeHelper.isTableAlreadyExistsException(t)) {
throw Throwables.propagate(t);
}
}
log.debug("Creating index if not exists on table {} for KeyValueStore {}", tableName, this);
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName,
"kv_create_table_index").execute();
}
private int doInsert(Handle handle, final ResolvedKey resolvedKey,
final VersionImpl schemaVersion, byte[] valueBytes, DateTime date) {
Long schemaVersionLong = schemaVersion != null ? schemaVersion.getInternalIdentifier() : 0L;
Update update =
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName, "kv_create");
update.bind("key_type", resolvedKey.getTypeTag());
update.bind("key_id_hi", resolvedKey.getIdentifierHi());
update.bind("key_id_lo", resolvedKey.getIdentifierLo());
update.bind("created_dt", date.withZone(DateTimeZone.UTC).getMillis() / 1000);
update.bind("version", 1L);
update.bind("schema_version", schemaVersionLong);
update.bind("value", valueBytes);
int inserted = update.execute();
return inserted;
}
private int doUpdate(Handle handle, final ResolvedKey resolvedKey,
final VersionImpl schemaVersion, byte[] valueBytes) {
Update update =
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName, "kv_update");
update.bind("key_type", resolvedKey.getTypeTag());
update.bind("key_id_hi", resolvedKey.getIdentifierHi());
update.bind("key_id_lo", resolvedKey.getIdentifierLo());
update.bind("schema_version", schemaVersion.getInternalIdentifier());
update.bind("updated_dt", getEpochSecondsNow());
update.bind("value", valueBytes);
int updated = update.execute();
return updated;
}
private int doUpdateVersioned(Handle handle, final ResolvedKey resolvedKey,
final VersionImpl version, final VersionImpl schemaVersion, byte[] valueBytes) {
Update update =
JDBIHelper.getBoundStatement(handle, getPrefix(), "kv_table_name", tableName,
"kv_update_versioned");
update.bind("key_type", resolvedKey.getTypeTag());
update.bind("key_id_hi", resolvedKey.getIdentifierHi());
update.bind("key_id_lo", resolvedKey.getIdentifierLo());
update.bind("updated_dt", getEpochSecondsNow());
update.bind("old_version", version.getInternalIdentifier());
update.bind("new_version", version.getInternalIdentifier() + 1L);
update.bind("schema_version", schemaVersion.getInternalIdentifier());
update.bind("value", valueBytes);
int updated = update.execute();
return updated;
}
private int getEpochSecondsNow() {
return (int) (new DateTime().withZone(DateTimeZone.UTC).getMillis() / 1000);
}
}