blob: ac98f2fb0dcda09f5c53a526fb087db1c3574080 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.rx.syncbase;
import android.content.Context;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import io.v.baku.toolkit.BakuActivityTrait;
import io.v.baku.toolkit.VAndroidContextTrait;
import io.v.baku.toolkit.blessings.BlessingsUtils;
import io.v.baku.toolkit.blessings.ClientUser;
import io.v.v23.context.VContext;
import io.v.v23.security.Blessings;
import io.v.v23.security.access.AccessList;
import io.v.v23.security.access.Permissions;
import io.v.v23.services.syncbase.nosql.SyncgroupMemberInfo;
import io.v.v23.services.syncbase.nosql.SyncgroupSpec;
import io.v.v23.services.syncbase.nosql.TableRow;
import java8.util.function.Function;
import java8.util.function.Supplier;
import java8.util.stream.Collectors;
import lombok.Value;
import lombok.experimental.Accessors;
import rx.Observable;
import rx.functions.Action2;
// TODO(rosswang): Generalize this to other possible syncgroup strategies.
@Accessors(prefix = "m")
public abstract class UserSyncgroup extends RxSyncgroup {
public static final SgSuffixFormat<Parameters> DEFAULT_SYNCGROUP_SUFFIX =
SgSuffixFormats.discriminated("user");
public static class Builder {
protected VContext mVContext;
protected Observable<Blessings> mRxBlessings;
protected SyncHostLevel mSyncHostLevel;
protected SgSuffixFormat<? super Parameters> mSgSuffixFormat =
DEFAULT_SYNCGROUP_SUFFIX;
protected RxDb mDb;
protected Function<String, String> mDescriptionForUsername = u -> "User syncgroup for " + u;
protected Function<AccessList, Permissions> mPermissionsForAcl;
protected List<TableRow> mPrefixes = new ArrayList<>();
protected SyncgroupMemberInfo mMemberInfo = DEFAULT_SYNCGROUP_MEMBER_INFO;
protected Action2<Integer, Throwable> mOnError;
// helper for constructing a default UserAppSyncHost if needed
protected Context mAndroidContext;
public Builder vContext(final VContext vContext) {
mVContext = vContext;
return this;
}
public Builder rxBlessings(final Observable<Blessings> rxBlessings) {
mRxBlessings = rxBlessings;
return this;
}
public Builder syncHostLevel(final SyncHostLevel syncHostLevel) {
mSyncHostLevel = syncHostLevel;
mAndroidContext = null;
return this;
}
public Builder sgSuffixFormat(final SgSuffixFormat<? super Parameters> sgSuffixFormat) {
mSgSuffixFormat = sgSuffixFormat;
return this;
}
public Builder sgSuffix(final String sgSuffix) {
return sgSuffixFormat(SgSuffixFormats.simple(sgSuffix));
}
public Builder discriminatedSgSuffix(final String customSuffix) {
return sgSuffixFormat(SgSuffixFormats.discriminated(customSuffix));
}
public Builder db(final RxDb db) {
mDb = db;
return this;
}
public Builder descriptionForUsername(
final Function<String, String> descriptionForUsername) {
mDescriptionForUsername = descriptionForUsername;
return this;
}
public Builder permissionsForAcl(
final Function<AccessList, Permissions> permissionsForAcl) {
mPermissionsForAcl = permissionsForAcl;
return this;
}
/**
* This setter is not additive.
*/
public Builder prefixes(final List<TableRow> prefixes) {
mPrefixes.clear();
mPrefixes.addAll(prefixes);
return this;
}
/**
* This setter is not additive.
*/
public Builder prefixes(final TableRow... prefixes) {
mPrefixes.clear();
mPrefixes.addAll(Arrays.asList(prefixes));
return this;
}
/**
* This is an additive setter.
*/
public Builder prefix(final TableRow prefix) {
mPrefixes.add(prefix);
return this;
}
/**
* This is an additive setter.
*/
public Builder prefix(final String tableName, final String rowPrefix) {
return prefix(new TableRow(tableName, rowPrefix));
}
/**
* This is an additive setter.
*/
public Builder prefix(final String tableName) {
return prefix(tableName, "");
}
public Builder memberInfo(final SyncgroupMemberInfo memberInfo) {
mMemberInfo = memberInfo;
return this;
}
public Builder onError(final Action2<Integer, Throwable> onError) {
mOnError = onError;
return this;
}
/**
* This is a composite setter for:
* <ul>
* <li>{@code vContext}</li>Context
* <li>{@code rxBlessings}</li>
* <li>{@code onError}</li>
* </ul>
* and should be called prior to any overrides for those fields.
*/
public Builder activity(final VAndroidContextTrait<?> t) {
mAndroidContext = t.getAndroidContext();
return vContext(t.getVContext())
.rxBlessings(t.getBlessingsProvider().getRxBlessings())
.onError(t.getErrorReporter()::onError);
}
/**
* In addition to those fields in {@link #activity(VAndroidContextTrait)}, this
* additionally sets:
* <ul>
* <li>{@code db}</li>
* <li>and adds to {@code prefixes}</li>
* </ul>
*/
public Builder activity(final BakuActivityTrait<?> t) {
return activity(t.getVAndroidContextTrait())
.db(t.getSyncbaseDb())
.prefix(t.getSyncbaseTableName());
}
protected Parameters buildParameters(
final Supplier<SyncHostLevel> defaultSyncHost,
final Function<AccessList, Permissions> defaultPermissionsForAcl) {
return new Parameters(mVContext, mRxBlessings,
mSyncHostLevel == null ? defaultSyncHost.get() : mSyncHostLevel,
mSgSuffixFormat, mDb, mDescriptionForUsername,
mPermissionsForAcl == null? defaultPermissionsForAcl : mPermissionsForAcl,
ImmutableList.copyOf(mPrefixes), mMemberInfo, mOnError);
}
public UserCloudSyncgroup buildCloud() {
return new UserCloudSyncgroup(buildParameters(
() -> ClientLevelCloudSync.DEFAULT,
acl -> BlessingsUtils.cloudSyngroupPermissions(acl,
UserCloudSyncgroup.DEBUG_SG_HOST_BLESSING)));
}
public UserPeerSyncgroup buildPeer() {
return new UserPeerSyncgroup(buildParameters(
() -> new UserAppSyncHost(mAndroidContext),
BlessingsUtils::syncgroupPermissions));
}
}
public static Builder builder() {
return new Builder();
}
@Value
public static class Parameters {
VContext mVContext;
Observable<Blessings> mRxBlessings;
SyncHostLevel mSyncHostLevel;
SgSuffixFormat<? super Parameters> mSgSuffixFormat;
RxDb mDb;
Function<String, String> mDescriptionForUsername;
Function<AccessList, Permissions> mPermissionsForAcl;
ImmutableList<TableRow> mPrefixes;
SyncgroupMemberInfo mMemberInfo;
Action2<Integer, Throwable> mOnError;
}
protected final Parameters mParams;
public UserSyncgroup(final Parameters params) {
super(params.getOnError());
mParams = params;
}
private SyncgroupSpec createSpec(final ClientUser clientUser, final AccessList acl) {
return new SyncgroupSpec(
mParams.getDescriptionForUsername().apply(clientUser.getUsername()),
mParams.getPermissionsForAcl().apply(acl), mParams.getPrefixes(),
mParams.getSyncHostLevel().getRendezvousTableNames(clientUser), false);
}
protected abstract Observable<?> rxJoin(final String sgHost, final String sgName,
final SyncgroupSpec spec);
private Observable<?> rxJoin(final ClientUser clientUser, final AccessList acl) {
final String sgHost = mParams.getSyncHostLevel().getSyncgroupHostName(clientUser);
final String sgName = RxSyncbase.syncgroupName(sgHost,
mParams.getSgSuffixFormat().get(mParams));
final SyncgroupSpec spec = createSpec(clientUser, acl);
return rxJoin(sgHost, sgName, spec);
}
@Override
public Observable<?> rxJoin() {
return Observable.switchOnNext(mParams.getRxBlessings()
.map(b -> {
final AccessList acl = BlessingsUtils.blessingsToAcl(mParams.getVContext(), b);
final List<Observable<?>> joins =
BlessingsUtils.blessingsToClientUserStream(mParams.getVContext(), b)
.distinct()
.map(cu -> rxJoin(cu, acl))
.collect(Collectors.toList());
if (joins.isEmpty()) {
throw new NoSuchElementException("UserSyncgroup requires a username; no " +
"username blessings found. Blessings: " + b);
}
return Observable.merge(joins);
}));
}
}