io.activej.ot.uplink.OTUplinkImpl 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.async.function.AsyncPredicate;
import io.activej.common.function.FunctionEx;
import io.activej.common.ref.Ref;
import io.activej.ot.OTCommit;
import io.activej.ot.OTCommitFactory.DiffsWithLevel;
import io.activej.ot.PollSanitizer;
import io.activej.ot.reducers.DiffsReducer;
import io.activej.ot.repository.OTRepository;
import io.activej.ot.system.OTSystem;
import io.activej.promise.Promise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Set;
import static io.activej.async.util.LogUtils.thisMethod;
import static io.activej.async.util.LogUtils.toLogger;
import static io.activej.common.Utils.*;
import static io.activej.common.exception.FatalErrorHandlers.handleError;
import static io.activej.ot.OTAlgorithms.*;
import static io.activej.ot.reducers.DiffsReducer.toSquashedList;
import static io.activej.promise.PromisePredicates.isResultOrException;
import static io.activej.promise.Promises.retry;
import static java.util.Collections.singleton;
public final class OTUplinkImpl implements OTUplink {
private static final Logger logger = LoggerFactory.getLogger(OTUplinkImpl.class);
private final OTSystem otSystem;
private final OTRepository repository;
private final FunctionEx, PC> protoCommitEncoder;
private final FunctionEx> protoCommitDecoder;
private OTUplinkImpl(OTRepository repository, OTSystem otSystem, FunctionEx, PC> protoCommitEncoder,
FunctionEx> protoCommitDecoder) {
this.otSystem = otSystem;
this.repository = repository;
this.protoCommitEncoder = protoCommitEncoder;
this.protoCommitDecoder = protoCommitDecoder;
}
public static OTUplinkImpl create(OTRepository repository, OTSystem otSystem,
FunctionEx, C> commitToObject, FunctionEx> objectToCommit) {
return new OTUplinkImpl<>(repository, otSystem, commitToObject, objectToCommit);
}
public static OTUplinkImpl> create(OTRepository repository, OTSystem otSystem) {
return new OTUplinkImpl<>(repository, otSystem, commit -> commit, object -> object);
}
public OTRepository getRepository() {
return repository;
}
@Override
public Promise createProtoCommit(K parent, List diffs, long parentLevel) {
return repository.createCommit(parent, new DiffsWithLevel<>(parentLevel, diffs))
.map(protoCommitEncoder)
.whenComplete(toLogger(logger, thisMethod(), parent, diffs, parentLevel));
}
@Override
public Promise> push(PC protoCommit) {
OTCommit commit;
try {
commit = protoCommitDecoder.apply(protoCommit);
} catch (Exception ex) {
handleError(ex, this);
return Promise.ofException(ex);
}
return repository.push(commit)
.then(repository::getHeads)
.then(initialHeads -> excludeParents(repository, otSystem, union(initialHeads, singleton(commit.getId())))
.then(heads -> mergeAndPush(repository, otSystem, heads))
.then(mergeHead -> {
Set mergeHeadSet = singleton(mergeHead);
return repository.updateHeads(mergeHeadSet, difference(initialHeads, mergeHeadSet))
.then(() -> doFetch(mergeHeadSet, commit.getId()));
}))
.whenComplete(toLogger(logger, thisMethod(), protoCommit));
}
@Override
public Promise> checkout() {
Ref> cachedSnapshotRef = new Ref<>();
return repository.getHeads()
.then(heads -> findParent(
repository,
otSystem,
heads,
DiffsReducer.toList(),
commit -> repository.loadSnapshot(commit.getId())
.map(maybeSnapshot -> (cachedSnapshotRef.value = maybeSnapshot.orElse(null)) != null)))
.then(findResult -> Promise.of(
new FetchData<>(
findResult.getChild(),
findResult.getChildLevel(),
concat(cachedSnapshotRef.value, findResult.getAccumulatedDiffs()))))
.then(checkoutData -> fetch(checkoutData.getCommitId())
.map(fetchData -> new FetchData<>(
fetchData.getCommitId(),
fetchData.getLevel(),
otSystem.squash(concat(checkoutData.getDiffs(), fetchData.getDiffs()))
))
)
.whenComplete(toLogger(logger, thisMethod()));
}
@Override
public Promise> fetch(K currentCommitId) {
return repository.getHeads()
.then(heads -> doFetch(heads, currentCommitId))
.whenComplete(toLogger(logger, thisMethod(), currentCommitId));
}
@Override
public Promise> poll(K currentCommitId) {
return retry(
isResultOrException((Set polledHeads) -> !polledHeads.contains(currentCommitId)),
PollSanitizer.create(repository.pollHeads()))
.then(heads -> doFetch(heads, currentCommitId));
}
private Promise> doFetch(Set heads, K currentCommitId) {
return findParent(
repository,
otSystem,
heads,
toSquashedList(otSystem),
AsyncPredicate.of(commit -> commit.getId().equals(currentCommitId)))
.map(findResult -> new FetchData<>(
findResult.getChild(),
findResult.getChildLevel(),
otSystem.squash(findResult.getAccumulatedDiffs())
))
.whenComplete(toLogger(logger, thisMethod(), currentCommitId));
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy