/**
* 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.journal;
import io.kazuki.v0.internal.availability.AvailabilityManager;
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.store.KazukiException;
import io.kazuki.v0.store.Key;
import io.kazuki.v0.store.keyvalue.KeyValueIterable;
import io.kazuki.v0.store.keyvalue.KeyValueIterator;
import io.kazuki.v0.store.keyvalue.KeyValuePair;
import io.kazuki.v0.store.keyvalue.KeyValueStore;
import io.kazuki.v0.store.keyvalue.KeyValueStoreConfiguration;
import io.kazuki.v0.store.keyvalue.KeyValueStoreIteration.SortDirection;
import io.kazuki.v0.store.keyvalue.KeyValueStoreJdbiH2Impl;
import io.kazuki.v0.store.lifecycle.Lifecycle;
import io.kazuki.v0.store.lifecycle.LifecycleRegistration;
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.Attribute;
import io.kazuki.v0.store.schema.model.IndexDefinition;
import io.kazuki.v0.store.schema.model.Schema;
import io.kazuki.v0.store.sequence.KeyImpl;
import io.kazuki.v0.store.sequence.ResolvedKey;
import io.kazuki.v0.store.sequence.SequenceService;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
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.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.inject.Provider;
public class PartitionedJournalStore implements JournalStore, LifecycleRegistration {
private final Logger log = LogTranslation.getLogger(getClass());
private final AvailabilityManager availability;
private final LockManager lockManager;
private final KazukiComponent<DataSource> dataSource;
private final IDBI database;
private final SqlTypeHelper typeHelper;
private final SequenceService sequence;
private final SchemaStore schema;
private KeyValueStore metaStore;
private final Lock nukeLock = new ReentrantLock();
private final String dbType;
private final String groupName;
private final String storeName;
private final boolean strictTypeCreation;
private final Long partitionSize;
private final String dataType;
private final String typeName;
private final AtomicReference<KeyValueStore> activePartitionStore;
private final AtomicReference<PartitionInfoImpl> activePartitionInfo;
private volatile Lifecycle lifecycle;
private final ComponentDescriptor<JournalStore> componentDescriptor;
@Inject
public PartitionedJournalStore(AvailabilityManager availability, LockManager lockManager,
KazukiComponent<DataSource> dataSource, IDBI database, SqlTypeHelper typeHelper,
SchemaStore schema, SequenceService sequence, KeyValueStoreConfiguration config) {
this(availability, lockManager, dataSource, database, typeHelper, schema, sequence, config
.getDbType(), config.getGroupName(), config.getStoreName(), config.getPartitionSize(),
config.getDataType(), config.isStrictTypeCreation());
}
public PartitionedJournalStore(AvailabilityManager availability, LockManager lockManager,
KazukiComponent<DataSource> dataSource, IDBI database, SqlTypeHelper typeHelper,
SchemaStore schema, SequenceService sequence, String dbType, String groupName,
String storeName, Long partitionSize, String dataType, boolean strictTypeCreation) {
Preconditions.checkNotNull(dataType, "dataType");
this.availability = availability;
this.lockManager = lockManager;
this.dataSource = dataSource;
this.database = database;
this.typeHelper = typeHelper;
this.schema = schema;
this.sequence = sequence;
this.dbType = dbType;
this.dataType = dataType;
this.groupName = groupName;
this.storeName = storeName;
this.strictTypeCreation = strictTypeCreation;
this.partitionSize = partitionSize;
this.typeName = "PartitionInfo-" + groupName + "-" + storeName;
this.activePartitionInfo = new AtomicReference<PartitionInfoImpl>();
this.activePartitionStore = new AtomicReference<KeyValueStore>();
this.componentDescriptor =
new ComponentDescriptorImpl<JournalStore>("KZ:JournalStore:" + groupName + "-" + storeName,
JournalStore.class, (JournalStore) this, new ImmutableList.Builder().add(
(new LateBindingComponentDescriptorImpl<Lifecycle>() {
@Override
public KazukiComponent<Lifecycle> get() {
return (KazukiComponent<Lifecycle>) PartitionedJournalStore.this.lifecycle;
}
}), ((KazukiComponent) lockManager).getComponentDescriptor(),
dataSource.getComponentDescriptor(),
((KazukiComponent) sequence).getComponentDescriptor(),
((KazukiComponent) schema).getComponentDescriptor()).build());
}
@Override
public Lifecycle getLifecycle() {
return this.lifecycle;
}
@Inject
public void register(Lifecycle lifecycle) {
if (this.lifecycle != null && !this.lifecycle.equals(lifecycle)) {
throw new IllegalStateException("lifecycle already registered with "
+ System.identityHashCode(this.lifecycle));
}
this.lifecycle = lifecycle;
this.lifecycle.register(new LifecycleSupportBase() {
@Override
public void init() {
PartitionedJournalStore.this.initialize();
}
});
}
@Override
public ComponentDescriptor<JournalStore> getComponentDescriptor() {
return this.componentDescriptor;
}
@Override
@Inject
public void registerAsComponent(ComponentRegistrar manager) {
manager.register(this.componentDescriptor);
}
@Override
public void initialize() {
log.debug("Intitializing PartitionedJournalStore {}", this);
try (LockManager toRelease = lockManager.acquire()) {
this.metaStore = getKeyValueStore("META", true);
try {
if (this.schema.retrieveSchema(this.typeName) == null) {
this.schema.createSchema(this.typeName, new Schema(Collections.<Attribute>emptyList(),
Collections.<IndexDefinition>emptyList()));
}
try (KeyValueIterable<PartitionInfoSnapshot> parts = this.getAllPartitions()) {
for (PartitionInfoSnapshot partition : parts) {
if (!partition.isClosed()) {
log.debug("Found active partition: {}", partition.getPartitionId());
this.activePartitionInfo.set(new PartitionInfoImpl(partition.getPartitionId(),
partition.getMinId(), partition.getMaxId(), partition.getSize(), partition
.isClosed()));
this.activePartitionStore
.set(getKeyValueStore(getPartitionName(sequence.resolveKey(KeyImpl
.valueOf(partition.getPartitionId()))), false));
break;
}
}
}
} catch (KazukiException e) {
throw Throwables.propagate(e);
}
}
availability.setAvailable(true);
log.debug("Intitialized PartitionedJournalStore {}", this);
}
@Override
public <T> Key append(String type, Class<T> clazz, T inValue, TypeValidation typeSafety)
throws KazukiException {
availability.assertAvailable();
if (!this.dataType.equals(type)) {
throw new IllegalArgumentException("invalid type: expected " + this.dataType + ", was "
+ type);
}
try (LockManager toRelease = lockManager.acquire()) {
Key theKey = sequence.nextKey(type);
ResolvedKey resolvedKey = sequence.resolveKey(theKey);
if (theKey == null) {
throw new IllegalStateException("unable to allocate new key of type: " + type);
}
PartitionInfoImpl theActivePartitionInfo = activePartitionInfo.get();
KeyValueStore targetStore = activePartitionStore.get();
if (theActivePartitionInfo == null) {
KeyImpl partitionKey = (KeyImpl) sequence.nextKey(this.typeName);
if (partitionKey == null) {
throw new IllegalStateException("unable to allocate new partition key of type: "
+ this.typeName);
}
ResolvedKey resolvedPartitionKey = sequence.resolveKey(partitionKey);
String partitionName = getPartitionName(resolvedPartitionKey);
theActivePartitionInfo =
new PartitionInfoImpl(partitionKey.getInternalIdentifier(),
resolvedKey.getIdentifierLo(), resolvedKey.getIdentifierLo(), 0L, false);
this.activePartitionInfo.set(theActivePartitionInfo);
this.metaStore.create(this.typeName, PartitionInfo.class,
theActivePartitionInfo.snapshot(), resolvedPartitionKey, TypeValidation.STRICT);
targetStore = getKeyValueStore(partitionName, true);
this.activePartitionStore.set(targetStore);
}
targetStore.create(type, clazz, inValue, resolvedKey, typeSafety);
theActivePartitionInfo.setMaxId(resolvedKey.getIdentifierLo());
theActivePartitionInfo.setSize(theActivePartitionInfo.getSize() + 1L);
boolean success =
this.metaStore.update(KeyImpl.valueOf(theActivePartitionInfo.getPartitionId()),
PartitionInfo.class, theActivePartitionInfo.snapshot());
if (!success) {
throw new KazukiException("unable to update partition info");
}
if (theActivePartitionInfo.getSize() >= this.partitionSize) {
this.closeActivePartition();
}
return theKey;
}
}
@Override
public <T> KeyValueIterable<KeyValuePair<T>> entriesAbsolute(String type, Class<T> clazz,
SortDirection sortDirection, Long offset, Long limit) throws KazukiException {
availability.assertAvailable();
if (sortDirection != null && SortDirection.DESCENDING.equals(sortDirection)) {
throw new IllegalArgumentException("absolute iterator only supports order ASCENDING");
}
if (!this.dataType.equals(type)) {
throw new IllegalArgumentException("invalid type: expected " + this.dataType + ", was "
+ type);
}
long idOffset = 0L;
if (offset != null) {
idOffset = offset.longValue();
}
idOffset += 1;
List<KeyValueIterable<KeyValuePair<T>>> iters =
new ArrayList<KeyValueIterable<KeyValuePair<T>>>();
try (KeyValueIterator<PartitionInfoSnapshot> iter =
this.getAllPartitions(sortDirection).iterator()) {
while (iter.hasNext() && (limit == null || limit > 0L)) {
PartitionInfo partition = iter.next();
if (idOffset >= partition.getMinId() && idOffset <= partition.getMaxId()) {
Long specificLimit = limit == null ? null : limit;
if (specificLimit != null) {
long contained = 1 + partition.getMaxId() - idOffset;
specificLimit = Math.min(contained, specificLimit);
limit -= specificLimit;
}
iters.add(new LazyIterable<KeyValuePair<T>>(getIterableProvider(type, clazz,
getPartitionName(sequence.resolveKey(KeyImpl.valueOf(partition.getPartitionId()))),
sortDirection, idOffset - partition.getMinId(), specificLimit)));
idOffset = partition.getMaxId() + 1;
}
}
}
if (iters.isEmpty()) {
return emptyKeyValueIterable();
}
return concatKeyValueIterables(iters);
}
@Override
public <T> KeyValueIterable<KeyValuePair<T>> entriesRelative(String type, Class<T> clazz,
SortDirection sortDirection, Long offset, Long limit) throws KazukiException {
availability.assertAvailable();
if (!this.dataType.equals(type)) {
throw new IllegalArgumentException("invalid type: expected " + this.dataType + ", was "
+ type);
}
long sizeOffset = 0L;
if (offset != null) {
sizeOffset = offset.longValue();
}
List<KeyValueIterable<KeyValuePair<T>>> iters =
new ArrayList<KeyValueIterable<KeyValuePair<T>>>();
try (KeyValueIterator<PartitionInfoSnapshot> iter =
this.getAllPartitions(sortDirection).iterator()) {
while (iter.hasNext() && (limit == null || limit > 0L)) {
PartitionInfo partition = iter.next();
long size = partition.getSize();
long toIgnore = Math.min(sizeOffset, size);
if (toIgnore == size) {
sizeOffset -= size;
continue;
}
Long specificLimit = limit == null ? null : limit;
if (specificLimit != null) {
long toTake = size - toIgnore;
specificLimit = Math.min(toTake, specificLimit);
limit -= specificLimit;
}
iters.add(new LazyIterable<KeyValuePair<T>>(getIterableProvider(type, clazz,
getPartitionName(sequence.resolveKey(KeyImpl.valueOf(partition.getPartitionId()))),
sortDirection, sizeOffset, specificLimit)));
sizeOffset = 0L;
}
}
if (iters.isEmpty()) {
return emptyKeyValueIterable();
}
return concatKeyValueIterables(iters);
}
@Override
public Long approximateSize() throws KazukiException {
availability.assertAvailable();
long size = 0L;
try (KeyValueIterable<PartitionInfoSnapshot> parts = this.getAllPartitions()) {
for (PartitionInfo partition : parts) {
size += partition.getSize();
}
}
return size;
}
@Override
public void clear() throws KazukiException {
log.debug("Clearing PartitionedJournalStore {}", this);
availability.assertAvailable();
try (LockManager toRelease = lockManager.acquire()) {
nukeLock.lock();
try {
this.closeActivePartition();
try (KeyValueIterable<PartitionInfoSnapshot> parts = this.getAllPartitions()) {
for (PartitionInfo partition : parts) {
if (!this.dropPartition(partition.getPartitionId())) {
throw new KazukiException("unable to delete partition");
}
}
}
if (sequence.hasType(this.dataType)) {
sequence.resetCounter(this.dataType);
}
if (sequence.hasType(this.typeName)) {
sequence.resetCounter(this.typeName);
}
metaStore.destroy();
this.activePartitionInfo.set(null);
this.activePartitionStore.set(null);
this.initialize();
} finally {
nukeLock.unlock();
}
}
log.debug("Cleared PartitionedJournalStore {}", this);
}
@Override
public boolean closeActivePartition() throws KazukiException {
log.debug("Closing Active Partition for PartitionedJournalStore {}", this);
availability.assertAvailable();
try (LockManager toRelease = lockManager.acquire()) {
PartitionInfoImpl partition = activePartitionInfo.get();
if (partition == null || partition.isClosed()) {
return false;
}
this.activePartitionInfo.set(null);
this.activePartitionStore.set(null);
partition.setClosed(true);
boolean result =
metaStore.update(KeyImpl.valueOf(partition.getPartitionId()), PartitionInfo.class,
partition);
if (result) {
log.debug("Closed Active Partition for PartitionedJournalStore {}", this);
}
return result;
}
}
@Override
public boolean dropPartition(String partitionId) throws KazukiException {
log.debug("Dropping Partition {} of PartitionedJournalStore {}", partitionId, this);
availability.assertAvailable();
try (LockManager toRelease = lockManager.acquire()) {
Key partitionKey = KeyImpl.valueOf(partitionId);
PartitionInfo partition = metaStore.retrieve(partitionKey, PartitionInfoSnapshot.class);
if (partition == null) {
return false;
}
if (!partition.isClosed()) {
throw new IllegalStateException("drop() applies to closed partitions only");
}
ResolvedKey resolvedKey = sequence.resolveKey(partitionKey);
KeyValueStore keyValue = getKeyValueStore(getPartitionName(resolvedKey), false);
keyValue.destroy();
boolean result = metaStore.delete(partitionKey);
if (result) {
log.debug("Dropped Partition {} of PartitionedJournalStore {}", partitionId, this);
}
return result;
}
}
@Override
@Nullable
public PartitionInfo getActivePartition() throws KazukiException {
availability.assertAvailable();
PartitionInfoImpl info = activePartitionInfo.get();
return info == null ? null : info.snapshot();
}
@Override
public KeyValueIterable<PartitionInfoSnapshot> getAllPartitions() throws KazukiException {
return getAllPartitions(SortDirection.ASCENDING);
}
@Override
public KeyValueIterable<PartitionInfoSnapshot> getAllPartitions(SortDirection sortDirection)
throws KazukiException {
availability.assertAvailable();
return metaStore.iterators().values(this.typeName, PartitionInfoSnapshot.class, sortDirection);
}
private KeyValueStore getKeyValueStore(String partitionName, boolean initialize) {
KeyValueStoreConfiguration.Builder config = new KeyValueStoreConfiguration.Builder();
config.withDbType(this.dbType);
config.withGroupName(this.groupName);
config.withStoreName(this.storeName);
config.withPartitionName(partitionName);
config.withPartitionSize(this.partitionSize);
config.withStrictTypeCreation(this.strictTypeCreation);
KeyValueStore keyValueStore =
new KeyValueStoreJdbiH2Impl(availability, lockManager, dataSource, database, typeHelper,
schema, sequence, config.build());
if (initialize) {
keyValueStore.initialize();
}
return keyValueStore;
}
private static String getPartitionName(ResolvedKey resolvedKey) {
return String.format("%016x", resolvedKey.getIdentifierLo());
}
private static <T> KeyValueIterable<T> emptyKeyValueIterable() {
return new KeyValueIterable<T>() {
@Override
public KeyValueIterator<T> iterator() {
return new KeyValueIterator<T>() {
@Override
public boolean hasNext() {
return false;
}
@Override
public T next() {
throw new IllegalStateException();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void close() {}
};
}
@Override
public void close() {}
};
}
private static <T> KeyValueIterable<T> concatKeyValueIterables(
final Collection<KeyValueIterable<T>> iterables) {
return new KeyValueIterable<T>() {
private final List<KeyValueIterable<T>> innerIterables = ImmutableList.copyOf(iterables);
private boolean instantiated = false;
@SuppressWarnings("unchecked")
@Override
public KeyValueIterator<T> iterator() {
if (instantiated) {
throw new IllegalStateException("iterable may only be used once!");
}
if (iterables.isEmpty()) {
return (KeyValueIterator<T>) emptyKeyValueIterable().iterator();
}
return new KeyValueIterator<T>() {
private final Iterator<KeyValueIterable<T>> outerIter = innerIterables.iterator();
private KeyValueIterator<T> innerIter = null;
private boolean initialized = false;
private void advance() {
while (outerIter.hasNext() && (innerIter == null || !innerIter.hasNext())) {
innerIter = outerIter.next().iterator();
if (innerIter.hasNext()) {
break;
}
}
}
@Override
public boolean hasNext() {
if (!initialized) {
advance();
initialized = true;
}
return innerIter.hasNext();
}
@Override
public T next() {
if (!hasNext()) {
throw new IllegalStateException("iterator has no next()");
}
T nextVal = innerIter.next();
if (!innerIter.hasNext()) {
advance();
}
return nextVal;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
for (KeyValueIterable<T> iter : innerIterables) {
iter.close();
}
}
};
}
@Override
public void close() {
for (KeyValueIterable<T> iter : innerIterables) {
iter.close();
}
}
};
}
private <T> Provider<KeyValueIterable<KeyValuePair<T>>> getIterableProvider(final String type,
final Class<T> clazz, final String partitionName, final SortDirection sortDirection,
final Long offset, final Long limit) {
return new Provider<KeyValueIterable<KeyValuePair<T>>>() {
@Override
public KeyValueIterable<KeyValuePair<T>> get() {
try {
return getKeyValueStore(partitionName, false).iterators().entries(type, clazz,
sortDirection, offset, limit);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public String toString() {
return "Provider<Iterable>(t=" + type + ",c=" + clazz.getName() + ",p=" + partitionName
+ ",o=" + offset + ",l=" + limit + ")";
}
};
}
public static class LazyIterable<T> implements KeyValueIterable<T> {
private final Provider<KeyValueIterable<T>> provider;
private KeyValueIterator<T> instance;
private boolean instantiated = false;
public LazyIterable(Provider<KeyValueIterable<T>> provider) {
this.provider = provider;
}
@Override
public KeyValueIterator<T> iterator() {
if (instantiated) {
throw new IllegalStateException("iterable may only be used once!");
}
if (this.instance == null) {
this.instance = provider.get().iterator();
this.instantiated = true;
}
return this.instance;
}
@Override
public void close() {
if (this.instantiated && this.instance != null) {
this.instance.close();
this.instance = null;
}
}
@Override
public String toString() {
return "LazyIterator(" + provider.toString() + ")";
}
}
}