/** * 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); } }