io.v.v23.syncbase.DatabaseImpl.java Source code

Java tutorial

Introduction

Here is the source code for io.v.v23.syncbase.DatabaseImpl.java

Source

// 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.v23.syncbase;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import io.v.impl.google.ListenableFutureCallback;
import io.v.impl.google.naming.NamingUtil;
import io.v.v23.InputChannel;
import io.v.v23.InputChannels;
import io.v.v23.VFutures;
import io.v.v23.context.VContext;
import io.v.v23.security.BlessingPattern;
import io.v.v23.security.access.AccessList;
import io.v.v23.security.access.Constants;
import io.v.v23.security.access.Permissions;
import io.v.v23.services.permissions.ObjectClient;
import io.v.v23.services.syncbase.BatchOptions;
import io.v.v23.services.syncbase.BatchHandle;
import io.v.v23.services.syncbase.BlobRef;
import io.v.v23.services.syncbase.CollectionRowPattern;
import io.v.v23.services.syncbase.Id;
import io.v.v23.services.syncbase.DatabaseClient;
import io.v.v23.services.syncbase.DatabaseClientFactory;
import io.v.v23.services.syncbase.SchemaMetadata;
import io.v.v23.services.syncbase.StoreChange;
import io.v.v23.services.watch.Change;
import io.v.v23.services.watch.ResumeMarker;
import io.v.v23.syncbase.util.Util;
import io.v.v23.vdl.ClientRecvStream;
import io.v.v23.vdl.Types;
import io.v.v23.vdl.VdlAny;
import io.v.v23.vdl.VdlOptional;
import io.v.v23.verror.NotImplementedException;
import io.v.v23.verror.VException;
import io.v.v23.vom.VomUtil;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

class DatabaseImpl implements Database, BatchDatabase {
    private native void nativeListenForInvites(VContext ctx, Database.InviteHandler handler) throws VException;

    private final String parentFullName;
    private final String fullName;
    private final Id id;
    private final BatchHandle batchHandle;
    private final Schema schema;

    private final DatabaseClient client;

    DatabaseImpl(String parentFullName, Id id, Schema schema, BatchHandle batchHandle) {
        this.parentFullName = parentFullName;
        this.fullName = NamingUtil.join(parentFullName, Util.encodeId(id));
        this.id = id;
        this.batchHandle = batchHandle;
        this.schema = schema;
        this.client = DatabaseClientFactory.getDatabaseClient(this.fullName);
    }

    private static List<String> splitInTwo(String str, String separator) {
        Iterator<String> iter = Splitter.on(separator).limit(2).split(str).iterator();
        return ImmutableList.of(iter.hasNext() ? iter.next() : "", iter.hasNext() ? iter.next() : "");
    }

    // Implements DatabaseCore interface.
    @Override
    public Id id() {
        return this.id;
    }

    @Override
    public String fullName() {
        return this.fullName;
    }

    @Override
    public Collection getCollection(VContext ctx, String name) {
        String blessing = Util.UserBlessingFromContext(ctx);
        Id id = new Id(blessing, name);
        return getCollection(id);
    }

    public Collection getCollection(Id collectionId) {
        return new CollectionImpl(this.fullName, collectionId, this.batchHandle);
    }

    @Override
    public ListenableFuture<List<Id>> listCollections(VContext ctx) {
        // See comment in v.io/v23/services/syncbase/nosql/service.vdl for why
        // we can't implement listCollections using Glob (via Util.listChildren).
        return client.listCollections(ctx, this.batchHandle);
    }

    @Override
    public ListenableFuture<QueryResults> exec(VContext ctx, String query) {
        return this.execInternal(ctx, query, null);
    }

    @Override
    public ListenableFuture<QueryResults> exec(VContext ctx, String query, List<Object> paramValues,
            List<Type> paramTypes) {
        Preconditions.checkNotNull(paramValues);
        Preconditions.checkNotNull(paramTypes);
        if (paramValues.size() != paramTypes.size()) {
            throw new IllegalArgumentException("Length of paramValues and paramTypes is not equal");
        }
        List<VdlAny> params = new ArrayList<VdlAny>();
        try {
            Iterator<Object> v = paramValues.iterator();
            Iterator<Type> t = paramTypes.iterator();
            while (v.hasNext() && t.hasNext()) {
                params.add(new VdlAny(VomUtil.valueOf(v.next(), t.next())));
            }
        } catch (VException e) {
            return VFutures.withUserLandChecks(ctx, Futures.<QueryResults>immediateFailedFuture(e));
        }
        return this.execInternal(ctx, query, params);
    }

    // Implements AccessController interface.
    @Override
    public ListenableFuture<Void> setPermissions(VContext ctx, Permissions perms, String version) {
        return client.setPermissions(ctx, perms, version);
    }

    @Override
    public ListenableFuture<Map<String, Permissions>> getPermissions(VContext ctx) {
        return VFutures.withUserLandChecks(ctx, Futures.transform(client.getPermissions(ctx),
                new Function<ObjectClient.GetPermissionsOut, Map<String, Permissions>>() {
                    @Override
                    public Map<String, Permissions> apply(ObjectClient.GetPermissionsOut perms) {
                        return ImmutableMap.of(perms.version, perms.perms);
                    }
                }));
    }

    // Implements Database interface.
    @Override
    public ListenableFuture<Boolean> exists(VContext ctx) {
        return client.exists(ctx);
    }

    @Override
    public ListenableFuture<Void> create(VContext ctx, Permissions perms) {
        VdlOptional metadataOpt = schema != null ? VdlOptional.of(schema.getMetadata())
                : new VdlOptional<SchemaMetadata>(Types.optionalOf(SchemaMetadata.VDL_TYPE));
        if (perms == null) {
            // Default to giving full permissions to the creator.
            String blessing = Util.UserBlessingFromContext(ctx);
            AccessList acl = new AccessList(ImmutableList.of(new BlessingPattern(blessing)),
                    ImmutableList.<String>of());
            perms = new Permissions(ImmutableMap.of(Constants.RESOLVE.getValue(), acl, Constants.READ.getValue(),
                    acl, Constants.WRITE.getValue(), acl, Constants.ADMIN.getValue(), acl));
        }
        return client.create(ctx, metadataOpt, perms);
    }

    @Override
    public ListenableFuture<Void> destroy(VContext ctx) {
        return client.destroy(ctx);
    }

    public ListenableFuture<BatchDatabase> beginBatch(VContext ctx, BatchOptions opts) {
        ListenableFuture<BatchHandle> batchFuture = client.beginBatch(ctx, opts);
        final String parentFullName = this.parentFullName;
        final Id id = this.id;
        final Schema schema = this.schema;
        return VFutures.withUserLandChecks(ctx,
                Futures.transform(batchFuture, new Function<BatchHandle, BatchDatabase>() {
                    @Override
                    public BatchDatabase apply(BatchHandle batchHandle) {
                        return new DatabaseImpl(parentFullName, id, schema, batchHandle);
                    }
                }));
    }

    @Override
    public InputChannel<WatchChange> watch(VContext ctx, ResumeMarker resumeMarker,
            List<CollectionRowPattern> patterns) {
        return InputChannels.transform(ctx, client.watchPatterns(ctx, resumeMarker, patterns),
                new InputChannels.TransformFunction<Change, WatchChange>() {
                    @Override
                    public WatchChange apply(Change change) throws VException {
                        if (change.getState() == io.v.v23.services.watch.Constants.INITIAL_STATE_SKIPPED) {
                            return null;
                        }
                        if (!change.getName().contains("/")) {
                            // TODO(ivanpi): Pipe collection and initial changes through.
                            return null;
                        }
                        return convertToWatchChange(change);
                    }
                });
    }

    @Override
    public InputChannel<WatchChange> watch(VContext ctx, List<CollectionRowPattern> patterns) {
        return this.watch(ctx, null, patterns);
    }

    @Override
    public ListenableFuture<ResumeMarker> getResumeMarker(VContext ctx) {
        return client.getResumeMarker(ctx, this.batchHandle);
    }

    @Override
    public void listenForInvites(VContext context, Database.InviteHandler handler) throws VException {
        nativeListenForInvites(context, handler);
    }

    @Override
    public Syncgroup getSyncgroup(Id sgId) {
        return new SyncgroupImpl(fullName, sgId);
    }

    @Override
    public ListenableFuture<List<Id>> listSyncgroups(VContext ctx) {
        return client.listSyncgroups(ctx);
    }

    @Override
    public ListenableFuture<BlobWriter> writeBlob(VContext ctx, BlobRef ref) {
        ListenableFuture<BlobRef> refFuture = ref == null ? client.createBlob(ctx) : Futures.immediateFuture(ref);
        return VFutures.withUserLandChecks(ctx, Futures.transform(refFuture, new Function<BlobRef, BlobWriter>() {
            @Override
            public BlobWriter apply(BlobRef ref) {
                return new BlobWriterImpl(client, ref);
            }
        }));
    }

    @Override
    public BlobReader readBlob(VContext ctx, BlobRef ref) throws VException {
        if (ref == null) {
            throw new VException("Must pass a non-null blob ref.");
        }
        return new BlobReaderImpl(client, ref);
    }

    @Override
    public ListenableFuture<Void> enforceSchema(final VContext ctx) {
        ListenableFutureCallback<Void> callback = new ListenableFutureCallback<>();
        VException error = new NotImplementedException(ctx);
        callback.onFailure(error);
        return callback.getFuture(ctx);
    }

    // Implements BatchDatabase.
    @Override
    public ListenableFuture<Void> commit(VContext ctx) {
        return client.commit(ctx, this.batchHandle);
    }

    @Override
    public ListenableFuture<Void> abort(VContext ctx) {
        return client.abort(ctx, this.batchHandle);
    }

    private WatchChange convertToWatchChange(Change watchChange) throws VException {
        Object value = watchChange.getValue().getElem();
        if (!(value instanceof StoreChange)) {
            throw new VException("Expected watch data to contain StoreChange, instead got: " + value);
        }
        StoreChange storeChange = (StoreChange) value;
        ChangeType changeType;
        switch (watchChange.getState()) {
        case io.v.v23.services.watch.Constants.EXISTS:
            changeType = ChangeType.PUT_CHANGE;
            break;
        case io.v.v23.services.watch.Constants.DOES_NOT_EXIST:
            changeType = ChangeType.DELETE_CHANGE;
            break;
        default:
            throw new VException("Unsupported watch change state: " + watchChange.getState());
        }
        String[] parts = watchChange.getName().split("/", 2);
        if (parts.length != 2) {
            throw new VException("Invalid collection-row pair: " + watchChange.getName());
        }
        Id collectionId = Util.decodeId(parts[0]);
        String rowName = parts[1];
        return new WatchChange(collectionId, rowName, changeType, storeChange.getValue(),
                watchChange.getResumeMarker(), storeChange.getFromSync(), watchChange.getContinued());
    }

    private ListenableFuture<QueryResults> execInternal(VContext ctx, String query, List<VdlAny> params) {
        final ClientRecvStream<List<VdlAny>, Void> stream = client.exec(ctx, this.batchHandle, query, params);
        return VFutures.withUserLandChecks(ctx,
                Futures.transform(stream.recv(), new AsyncFunction<List<VdlAny>, QueryResults>() {
                    @Override
                    public ListenableFuture<QueryResults> apply(List<VdlAny> columnNames) throws Exception {
                        return Futures.immediateFuture((QueryResults) new QueryResultsImpl(columnNames, stream));
                    }
                }));
    }

    private static class QueryResultsImpl implements QueryResults {
        private final InputChannel<List<VdlAny>> input;
        private final List<String> columnNames;

        private QueryResultsImpl(List<VdlAny> columnNames, InputChannel<List<VdlAny>> input) throws VException {
            this.input = input;
            this.columnNames = new ArrayList<>(columnNames.size());
            for (int i = 0; i < columnNames.size(); ++i) {
                Object elem = columnNames.get(i).getElem();
                if (elem instanceof String) {
                    this.columnNames.add((String) elem);
                } else {
                    throw new VException("Expected first row in exec() stream to contain column "
                            + "names (of type String), got type: " + elem.getClass());
                }
            }
        }

        @Override
        public ListenableFuture<List<VdlAny>> recv() {
            return input.recv();
        }

        @Override
        public List<String> columnNames() {
            return columnNames;
        }
    }

}