blob: a6251e5d91024400a78de6e3a6e1a3c207e496e7 [file] [log] [blame]
// Copyright 2016 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.syncbase;
import io.v.syncbase.core.Permissions;
import io.v.syncbase.core.VError;
import io.v.syncbase.exception.SyncbaseException;
import io.v.v23.verror.VException;
import io.v.v23.vom.VomUtil;
import static io.v.syncbase.exception.Exceptions.chainThrow;
/**
* Represents an ordered set of key-value pairs.
* To get a Collection handle, call {@code Database.collection}.
*/
public class Collection {
private final io.v.syncbase.core.Collection mCoreCollection;
private final DatabaseHandle mDatabaseHandle;
private final Id mId;
Collection(io.v.syncbase.core.Collection coreCollection, DatabaseHandle databaseHandle) {
mCoreCollection = coreCollection;
mDatabaseHandle = databaseHandle;
mId = new Id(coreCollection.id());
}
void createIfMissing() throws SyncbaseException {
try {
mCoreCollection.create(Syncbase.defaultCollectionPerms());
} catch (VError vError) {
if (vError.id.equals(VError.EXIST)) {
return;
}
chainThrow("creating collection", vError);
}
}
/**
* Returns the id of this collection.
*/
public Id getId() {
return mId;
}
/**
* Shortcut for {@code Database.getSyncgroup(collection.getId())}, helpful for the common case
* of one syncgroup per collection.
* @throws IllegalArgumentException if this is collection on a batch database.
*/
public Syncgroup getSyncgroup() {
if (mDatabaseHandle instanceof BatchDatabase) {
throw new IllegalArgumentException("Must not call getSyncgroup within batch");
}
return ((Database) mDatabaseHandle).getSyncgroup(getId());
}
// TODO(sadovsky): Add deleteRange API.
// TODO(sadovsky): Maybe add scan API, if developers aren't satisfied with watch.
// TODO(sadovsky): Revisit the get API:
// - Is the Class<T> argument necessary?
// - Should we take the target Object as an argument, to avoid allocations?
// - What should it do if there is no value for the given key? (Currently, it returns null.)
/**
* Returns the value associated with {@code key}.
*/
public <T> T get(String key, Class<T> cls) throws SyncbaseException {
try {
return (T) VomUtil.decode(mCoreCollection.get(key), cls);
} catch (VError vError) {
if (vError.id.equals(VError.NO_EXIST)) {
return null;
}
chainThrow("getting value from collection", mId.getName(), vError);
} catch (VException e) {
chainThrow("decoding value retrieved from collection", mId.getName(), e);
}
throw new AssertionError("never happens");
}
/**
* Returns true if there is a value associated with {@code key}.
*/
public boolean exists(String key) throws SyncbaseException {
try {
return mCoreCollection.row(key).exists();
} catch (VError e) {
chainThrow("checking if value exists in collection", mId.getName(), e);
throw new AssertionError("never happens");
}
}
/**
* Puts {@code value} for {@code key}, overwriting any existing value. Idempotent.
*/
public <T> void put(String key, T value) throws SyncbaseException {
try {
mCoreCollection.put(key, VomUtil.encode(value, value.getClass()));
} catch (VError e) {
chainThrow("putting value into collection", mId.getName(), e);
} catch (VException e) {
chainThrow("putting value into collection", mId.getName(), e);
}
}
/**
* Deletes the value associated with {@code key}. Idempotent.
*/
public void delete(String key) throws SyncbaseException {
try {
mCoreCollection.delete(key);
} catch (VError e) {
chainThrow("deleting collection", mId.getName(), e);
}
}
/**
* FOR ADVANCED USERS. Returns the {@code AccessList} for this collection. Users should
* typically manipulate access lists via {@code collection.getSyncgroup()}.
*/
public AccessList getAccessList() throws SyncbaseException {
try {
return new AccessList(mCoreCollection.getPermissions());
} catch (VError e) {
chainThrow("getting access list of collection", mId.getName(), e);
throw new AssertionError("never happens");
}
}
/**
* FOR ADVANCED USERS. Updates the {@code AccessList} for this collection. Users should
* typically manipulate access lists via {@code collection.getSyncgroup()}.
*/
public void updateAccessList(final AccessList delta) throws SyncbaseException {
final Id id = this.getId();
Database.BatchOperation op = new Database.BatchOperation() {
@Override
public void run(BatchDatabase db) throws SyncbaseException {
io.v.syncbase.core.Collection coreCollection = db.getCollection(id)
.mCoreCollection;
try {
Permissions newPermissions = AccessList.applyDeltaForCollection(
coreCollection.getPermissions(), delta);
coreCollection.setPermissions(newPermissions);
} catch (VError vError) {
chainThrow("setting permissions in collection", id.getName(), vError);
}
}
};
// Create a batch if we're not already in a batch.
if (mDatabaseHandle instanceof BatchDatabase) {
op.run((BatchDatabase) mDatabaseHandle);
} else {
((Database) mDatabaseHandle).runInBatch(op);
}
}
}