io.activej.ot.uplink.OTUplinkStorage Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of activej-ot Show documentation
Show all versions of activej-ot Show documentation
Implementation of operational transformation technology. Allows building collaborative software systems.
/*
* Copyright (C) 2020 ActiveJ LLC.
*
* 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.activej.ot.uplink;
import io.activej.ot.TransformResult;
import io.activej.ot.system.OTSystem;
import io.activej.ot.uplink.OTUplinkStorage.Storage.SyncData;
import io.activej.promise.Promise;
import io.activej.promise.Promises;
import io.activej.promise.SettablePromise;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static io.activej.common.Utils.concat;
import static io.activej.promise.PromisePredicates.isResultOrException;
import static io.activej.promise.Promises.retry;
import static java.util.Collections.emptyList;
@SuppressWarnings("WeakerAccess")
public final class OTUplinkStorage implements OTUplink> {
public static final long FIRST_COMMIT_ID = 1L;
public static final int NO_LEVEL = 0;
public static final class ProtoCommit {
private final long id;
private final List diffs;
private ProtoCommit(long id, List diffs) {
this.id = id;
this.diffs = diffs;
}
public long getId() {
return id;
}
public List getDiffs() {
return diffs;
}
}
public interface Storage {
Promise init(long commitId, List snapshot, K uplinkCommitId, long uplinkLevel);
Promise<@Nullable FetchData> getSnapshot();
Promise getHead();
default Promise> fetch(long commitId) {
List diffs = new ArrayList<>();
return getHead()
.then(headCommitId ->
Promises.loop(commitId + 1L,
i -> i <= headCommitId,
i -> getCommit(i)
.map(commit -> {
diffs.addAll(commit.getDiffs());
return i + 1;
}))
.map($ -> new FetchData<>(headCommitId, NO_LEVEL, diffs)));
}
default Promise> poll(long currentCommitId) {
return fetch(currentCommitId);
}
Promise> getCommit(long commitId);
Promise add(long commitId, List diffs);
final class SyncData {
private final long commitId;
private final K uplinkCommitId;
private final long uplinkLevel;
private final List uplinkDiffs;
private final @Nullable Object protoCommit;
public SyncData(long id, K uplinkCommitId, long uplinkLevel, List uplinkDiffs, @Nullable Object protoCommit) {
this.commitId = id;
this.uplinkCommitId = uplinkCommitId;
this.uplinkLevel = uplinkLevel;
this.uplinkDiffs = uplinkDiffs;
this.protoCommit = protoCommit;
}
public long getCommitId() {
return commitId;
}
public K getUplinkCommitId() {
return uplinkCommitId;
}
public long getUplinkLevel() {
return uplinkLevel;
}
public List getUplinkDiffs() {
return uplinkDiffs;
}
public @Nullable Object getProtoCommit() {
return protoCommit;
}
public boolean isSyncing() {
return protoCommit != null;
}
}
Promise> getSyncData();
default Promise isSyncing() {
return getSyncData().map(SyncData::isSyncing);
}
Promise startSync(long headCommitId, K uplinkCommitId, Object protoCommit);
Promise completeSync(long commitId, List diffs, K uplinkCommitId, long uplinkLevel, List uplinkDiffs);
}
private final Storage storage;
private final OTSystem otSystem;
private final OTUplink uplink;
private OTUplinkStorage(Storage storage, OTSystem otSystem, OTUplink uplink) {
this.otSystem = otSystem;
this.storage = storage;
//noinspection unchecked
this.uplink = (OTUplink) uplink;
}
public Promise sync() {
return startSync()
.then(syncData -> uplink.push(syncData.getProtoCommit())
.then(uplinkFetchedData -> Promise.ofCallback(cb ->
completeSync(syncData.getCommitId(), new ArrayList<>(), uplinkFetchedData.getCommitId(), uplinkFetchedData.getLevel(), uplinkFetchedData.getDiffs(), cb))));
}
Promise> startSync() {
return storage.getSyncData()
.then(syncData -> {
if (syncData.getProtoCommit() != null) {
return Promise.of(syncData);
}
return storage.fetch(syncData.getCommitId())
.then(fetchedData -> {
long headCommitId = fetchedData.getCommitId();
List diffs = fetchedData.getDiffs();
return uplink.createProtoCommit(syncData.getUplinkCommitId(), concat(syncData.getUplinkDiffs(), diffs), 0)
.then(protoCommit ->
storage.startSync(headCommitId, syncData.getUplinkCommitId(), protoCommit)
.map($ -> new SyncData<>(headCommitId, syncData.getUplinkCommitId(), syncData.getUplinkLevel(), diffs, protoCommit)));
});
});
}
void completeSync(long commitId, List accumulatedDiffs, K uplinkCommitId, long uplinkLevel, List uplinkDiffs, SettablePromise cb) {
storage.fetch(commitId)
.whenResult(fetchData -> {
TransformResult transformResult = otSystem.transform(uplinkDiffs, fetchData.getDiffs());
accumulatedDiffs.addAll(transformResult.left);
storage.completeSync(fetchData.getCommitId(), accumulatedDiffs, uplinkCommitId, uplinkLevel, transformResult.right)
.whenResult(ok -> {
if (ok) {
cb.set(null);
} else {
completeSync(commitId, accumulatedDiffs, uplinkCommitId, uplinkLevel, transformResult.right, cb);
}
})
.whenException(cb::setException);
})
.whenException(cb::setException);
}
@Override
public Promise> checkout() {
return retry(
isResultOrException(Objects::nonNull),
() -> storage.getSnapshot()
.then(snapshotData -> snapshotData != null ?
Promise.of(snapshotData) :
uplink.checkout()
.then(uplinkSnapshotData -> storage.init(FIRST_COMMIT_ID, uplinkSnapshotData.getDiffs(), uplinkSnapshotData.getCommitId(), uplinkSnapshotData.getLevel())
.then(ok -> Promise.of(ok ?
new FetchData<>(FIRST_COMMIT_ID, NO_LEVEL, uplinkSnapshotData.getDiffs()) :
null)))))
.then(snapshotData ->
storage.fetch(snapshotData.getCommitId())
.map(fetchData ->
new FetchData<>(fetchData.getCommitId(), NO_LEVEL, concat(snapshotData.getDiffs(), fetchData.getDiffs()))));
}
@Override
public Promise> createProtoCommit(Long parentCommitId, List diffs, long parentLevel) {
return Promise.of(new ProtoCommit<>(parentCommitId, diffs));
}
@Override
public Promise> push(ProtoCommit protoCommit) {
return Promise.ofCallback(cb -> doPush(protoCommit.getId(), protoCommit.getDiffs(), emptyList(), cb));
}
void doPush(long commitId, List diffs, List fetchedDiffs, SettablePromise> cb) {
storage.add(commitId, diffs)
.whenResult(ok -> {
if (ok) {
cb.set(new FetchData<>(commitId + 1, NO_LEVEL, fetchedDiffs));
} else {
storage.fetch(commitId)
.whenResult(fetchData -> {
TransformResult transformResult = otSystem.transform(fetchData.getDiffs(), diffs);
doPush(fetchData.getCommitId(), transformResult.left, concat(fetchedDiffs, transformResult.right), cb);
})
.whenException(cb::setException);
}
})
.whenException(cb::setException);
}
@Override
public Promise> fetch(Long currentCommitId) {
return storage.fetch(currentCommitId);
}
@Override
public Promise> poll(Long currentCommitId) {
return storage.poll(currentCommitId);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy