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