Public API throws more useful exceptions.
Public API methods throw a variety of exceptions, allowing the client
programmer to take appropriate action if necessary in response to the
exceptions.
A new `io.v.syncbase.exception` package contains these exceptions,
plus an `Exceptions` utility that has a number of overloaded
`chainThrow` methods that used the chained exception pattern, throwing
an appropriate higher-level exception corresponding to each
lower-level error from the Go code.
See included README.md for details of the exceptions.
The exceptions thrown from the API have a message that includes both a
high-level message related to the public API where it was called and a
low-level message from the underlying VError or VException.
Change-Id: I128cafb9c73e4e12909c3de62b3fe1a5ed5e0b13
diff --git a/syncbase/src/main/java/io/v/syncbase/BatchDatabase.java b/syncbase/src/main/java/io/v/syncbase/BatchDatabase.java
index 91e8892..81b7f1a 100644
--- a/syncbase/src/main/java/io/v/syncbase/BatchDatabase.java
+++ b/syncbase/src/main/java/io/v/syncbase/BatchDatabase.java
@@ -5,6 +5,9 @@
package io.v.syncbase;
import io.v.syncbase.core.VError;
+import io.v.syncbase.exception.SyncbaseException;
+
+import static io.v.syncbase.exception.Exceptions.chainThrow;
/**
* Provides a way to perform a set of operations atomically on a database. See
@@ -19,10 +22,10 @@
}
/**
- * @throws IllegalArgumentException if opts.withoutSyncgroup not false
+ * @throws IllegalArgumentException if opts.withoutSyncgroup false
*/
@Override
- public Collection collection(String name, CollectionOptions opts) throws VError {
+ public Collection collection(String name, CollectionOptions opts) throws SyncbaseException {
if (!opts.withoutSyncgroup) {
throw new IllegalArgumentException("Cannot create syncgroup in a batch");
}
@@ -35,9 +38,15 @@
* Persists the pending changes to Syncbase. If the batch is read-only, {@code commit} will
* throw {@code ConcurrentBatchException}; abort should be used instead.
*/
- public void commit() throws VError {
- // TODO(sadovsky): Throw ConcurrentBatchException where appropriate.
- mCoreBatchDatabase.commit();
+ public void commit() throws SyncbaseException {
+ try {
+
+ // TODO(sadovsky): Throw ConcurrentBatchException where appropriate.
+ mCoreBatchDatabase.commit();
+
+ } catch (VError e) {
+ chainThrow("committing batch", e);
+ }
}
/**
@@ -45,7 +54,13 @@
* strictly required, but may allow Syncbase to release locks or other resources sooner than if
* {@code abort} was not called.
*/
- public void abort() throws VError {
- mCoreBatchDatabase.abort();
+ public void abort() throws SyncbaseException {
+ try {
+
+ mCoreBatchDatabase.abort();
+
+ } catch (VError e) {
+ chainThrow("aborting batch", e);
+ }
}
}
diff --git a/syncbase/src/main/java/io/v/syncbase/Collection.java b/syncbase/src/main/java/io/v/syncbase/Collection.java
index 002da0b..8742522 100644
--- a/syncbase/src/main/java/io/v/syncbase/Collection.java
+++ b/syncbase/src/main/java/io/v/syncbase/Collection.java
@@ -6,9 +6,12 @@
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}.
@@ -24,14 +27,14 @@
mId = new Id(coreCollection.id());
}
- void createIfMissing() {
+ void createIfMissing() throws SyncbaseException {
try {
mCoreCollection.create(Syncbase.defaultCollectionPerms());
} catch (VError vError) {
if (vError.id.equals(VError.EXIST)) {
return;
}
- throw new RuntimeException("Failed to create collection", vError);
+ chainThrow("creating collection", vError);
}
}
@@ -65,68 +68,94 @@
/**
* Returns the value associated with {@code key}.
*/
- public <T> T get(String key, Class<T> cls) throws VError {
+ 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;
}
- throw vError;
+ chainThrow("getting value from collection", mId, vError);
} catch (VException e) {
- throw new VError(e);
+ chainThrow("decoding value retrieved from collection", mId, e);
}
+ throw new AssertionError("never happens");
}
/**
* Returns true if there is a value associated with {@code key}.
*/
- public boolean exists(String key) throws VError {
- return mCoreCollection.row(key).exists();
+ public boolean exists(String key) throws SyncbaseException {
+ try {
+
+ return mCoreCollection.row(key).exists();
+
+ } catch (VError e) {
+ chainThrow("checking if value exists in collection", mId, 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 VError {
+ 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, e);
} catch (VException e) {
- throw new VError(e);
+ chainThrow("putting value into collection", mId, e);
}
}
/**
* Deletes the value associated with {@code key}. Idempotent.
*/
- public void delete(String key) throws VError {
- mCoreCollection.delete(key);
+ public void delete(String key) throws SyncbaseException {
+ try {
+
+ mCoreCollection.delete(key);
+
+ } catch (VError e) {
+ chainThrow("deleting collection", mId, 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 VError {
- return new AccessList(mCoreCollection.getPermissions());
+ public AccessList getAccessList() throws SyncbaseException {
+ try {
+
+ return new AccessList(mCoreCollection.getPermissions());
+
+ } catch (VError e) {
+ chainThrow("getting access list of collection", mId, 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 VError {
+ 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) {
- io.v.syncbase.core.Collection coreCollection = db.getCollection(id).mCoreCollection;
+ 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) {
- throw new RuntimeException("updateAccessList failed", vError);
+ chainThrow("setting permissions in collection", id, vError);
}
}
};
diff --git a/syncbase/src/main/java/io/v/syncbase/Database.java b/syncbase/src/main/java/io/v/syncbase/Database.java
index 1fea773..8985189 100644
--- a/syncbase/src/main/java/io/v/syncbase/Database.java
+++ b/syncbase/src/main/java/io/v/syncbase/Database.java
@@ -17,6 +17,9 @@
import io.v.syncbase.core.CollectionRowPattern;
import io.v.syncbase.core.SyncgroupMemberInfo;
import io.v.syncbase.core.VError;
+import io.v.syncbase.exception.SyncbaseException;
+
+import static io.v.syncbase.exception.Exceptions.chainThrow;
/**
* A set of collections and syncgroups.
@@ -35,19 +38,19 @@
mCoreDatabase = coreDatabase;
}
- void createIfMissing() throws VError {
+ void createIfMissing() throws SyncbaseException {
try {
mCoreDatabase.create(Syncbase.defaultDatabasePerms());
} catch (VError vError) {
if (vError.id.equals(VError.EXIST)) {
return;
}
- throw vError;
+ chainThrow("creating database", vError);
}
}
@Override
- public Collection collection(String name, CollectionOptions opts) throws VError {
+ public Collection collection(String name, CollectionOptions opts) throws SyncbaseException {
Collection res = getCollection(new Id(Syncbase.getPersonalBlessingString(), name));
res.createIfMissing();
// TODO(sadovsky): Unwind collection creation on syncgroup creation failure? It would be
@@ -77,7 +80,7 @@
* @return the syncgroup
*/
public Syncgroup syncgroup(String name, List<Collection> collections, SyncgroupOptions opts)
- throws VError {
+ throws SyncbaseException {
if (collections.isEmpty()) {
throw new IllegalArgumentException("No collections specified");
}
@@ -99,7 +102,7 @@
/**
* Calls {@code syncgroup(name, collections, opts)} with default {@code SyncgroupOptions}.
*/
- public Syncgroup syncgroup(String name, List<Collection> collections) throws VError {
+ public Syncgroup syncgroup(String name, List<Collection> collections) throws SyncbaseException {
return syncgroup(name, collections, new SyncgroupOptions());
}
@@ -116,12 +119,19 @@
/**
* Returns an iterator over all syncgroups in the database.
*/
- public Iterator<Syncgroup> getSyncgroups() throws VError {
- ArrayList<Syncgroup> syncgroups = new ArrayList<>();
- for (io.v.syncbase.core.Id id : mCoreDatabase.listSyncgroups()) {
- syncgroups.add(getSyncgroup(new Id(id)));
+ public Iterator<Syncgroup> getSyncgroups() throws SyncbaseException {
+ try {
+
+ ArrayList<Syncgroup> syncgroups = new ArrayList<>();
+ for (io.v.syncbase.core.Id id : mCoreDatabase.listSyncgroups()) {
+ syncgroups.add(getSyncgroup(new Id(id)));
+ }
+ return syncgroups.iterator();
+
+ } catch (VError e) {
+ chainThrow("getting syncgroups of database", mCoreDatabase.id(), e);
+ throw new AssertionError("never happens");
}
- return syncgroups.iterator();
}
/**
@@ -294,7 +304,7 @@
* Designed for use in {@code runInBatch}.
*/
public interface BatchOperation {
- void run(BatchDatabase db);
+ void run(BatchDatabase db) throws SyncbaseException;
}
/**
@@ -304,13 +314,23 @@
* @param op the operation to run
* @param opts options for this batch
*/
- public void runInBatch(final BatchOperation op, BatchOptions opts) throws VError {
- mCoreDatabase.runInBatch(new io.v.syncbase.core.Database.BatchOperation() {
- @Override
- public void run(io.v.syncbase.core.BatchDatabase batchDatabase) {
- op.run(new BatchDatabase(batchDatabase));
- }
- }, opts.toCore());
+ public void runInBatch(final BatchOperation op, BatchOptions opts) throws SyncbaseException {
+ try {
+
+ mCoreDatabase.runInBatch(new io.v.syncbase.core.Database.BatchOperation() {
+ @Override
+ public void run(io.v.syncbase.core.BatchDatabase batchDatabase) {
+ try {
+ op.run(new BatchDatabase(batchDatabase));
+ } catch (SyncbaseException e) {
+ e.printStackTrace();
+ }
+ }
+ }, opts.toCore());
+
+ } catch (VError e) {
+ chainThrow("running batch operation in database", mCoreDatabase.id(), e);
+ }
}
/**
@@ -320,7 +340,7 @@
*
* @param op the operation to run
*/
- public void runInBatch(final BatchOperation op) throws VError {
+ public void runInBatch(final BatchOperation op) throws SyncbaseException {
runInBatch(op, new BatchOptions());
}
@@ -348,8 +368,15 @@
* @param opts options for this batch
* @return the batch handle
*/
- public BatchDatabase beginBatch(BatchOptions opts) throws VError {
- return new BatchDatabase(mCoreDatabase.beginBatch(opts.toCore()));
+ public BatchDatabase beginBatch(BatchOptions opts) throws SyncbaseException {
+ try {
+
+ return new BatchDatabase(mCoreDatabase.beginBatch(opts.toCore()));
+
+ } catch (VError e) {
+ chainThrow("creating batch in database", mCoreDatabase.id(), e);
+ throw new AssertionError("never happens");
+ }
}
/**
diff --git a/syncbase/src/main/java/io/v/syncbase/DatabaseHandle.java b/syncbase/src/main/java/io/v/syncbase/DatabaseHandle.java
index b12cec5..b31f96b 100644
--- a/syncbase/src/main/java/io/v/syncbase/DatabaseHandle.java
+++ b/syncbase/src/main/java/io/v/syncbase/DatabaseHandle.java
@@ -8,6 +8,9 @@
import java.util.Iterator;
import io.v.syncbase.core.VError;
+import io.v.syncbase.exception.SyncbaseException;
+
+import static io.v.syncbase.exception.Exceptions.chainThrow;
/**
@@ -50,12 +53,13 @@
* @param opts options for collection creation
* @return the collection handle
*/
- public abstract Collection collection(String name, CollectionOptions opts) throws VError;
+ public abstract Collection collection(String name, CollectionOptions opts)
+ throws SyncbaseException;
/**
* Calls {@code collection(name, opts)} with default {@code CollectionOptions}.
*/
- public Collection collection(String name) throws VError {
+ public Collection collection(String name) throws SyncbaseException {
return collection(name, new CollectionOptions());
}
@@ -72,11 +76,18 @@
/**
* Returns an iterator over all collections in the database.
*/
- public Iterator<Collection> getCollections() throws VError {
- ArrayList<Collection> collections = new ArrayList<>();
- for (io.v.syncbase.core.Id id : mCoreDatabaseHandle.listCollections()) {
- collections.add(getCollection(new Id(id)));
+ public Iterator<Collection> getCollections() throws SyncbaseException {
+ try {
+
+ ArrayList<Collection> collections = new ArrayList<>();
+ for (io.v.syncbase.core.Id id : mCoreDatabaseHandle.listCollections()) {
+ collections.add(getCollection(new Id(id)));
+ }
+ return collections.iterator();
+
+ } catch (VError e) {
+ chainThrow("getting collections in database", mCoreDatabaseHandle.id(), e);
+ throw new AssertionError("never happens");
}
- return collections.iterator();
}
}
diff --git a/syncbase/src/main/java/io/v/syncbase/Id.java b/syncbase/src/main/java/io/v/syncbase/Id.java
index 149c107..f7716d5 100644
--- a/syncbase/src/main/java/io/v/syncbase/Id.java
+++ b/syncbase/src/main/java/io/v/syncbase/Id.java
@@ -27,7 +27,7 @@
public static Id decode(String encodedId) {
String[] parts = encodedId.split(SEPARATOR);
if (parts.length != 2) {
- throw new IllegalArgumentException("Invalid encoded id: " + encodedId);
+ throw new IllegalArgumentException("Invalid encoded ID: \"" + encodedId + "\"");
}
return new Id(parts[0], parts[1]);
}
diff --git a/syncbase/src/main/java/io/v/syncbase/Syncbase.java b/syncbase/src/main/java/io/v/syncbase/Syncbase.java
index c0e93b7..d889060 100644
--- a/syncbase/src/main/java/io/v/syncbase/Syncbase.java
+++ b/syncbase/src/main/java/io/v/syncbase/Syncbase.java
@@ -27,9 +27,12 @@
import io.v.syncbase.core.Permissions;
import io.v.syncbase.core.Service;
import io.v.syncbase.core.VError;
+import io.v.syncbase.exception.SyncbaseException;
import io.v.syncbase.internal.Blessings;
import io.v.syncbase.internal.Neighborhood;
+import static io.v.syncbase.exception.Exceptions.chainThrow;
+
// FIXME(sadovsky): Currently, various methods throw RuntimeException on any error. We need to
// decide which error types to surface to clients, and define specific Exception subclasses for
// those.
@@ -44,6 +47,10 @@
/**
* Syncbase is a storage system for developers that makes it easy to synchronize app data between
* devices. It works even when devices are not connected to the Internet.
+ *
+ * <p>Methods of classes in this package may throw an exception that is a subclass of
+ * SyncbaseException. See details of those subclasses to determine whether there are conditions
+ * the calling code should handle.</p>
*/
public class Syncbase {
/**
@@ -113,7 +120,7 @@
DB_NAME = "db",
USERDATA_SYNCGROUP_NAME = "userdata__";
- private static Map selfAndCloud() throws VError {
+ private static Map selfAndCloud() throws SyncbaseException {
return ImmutableMap.of(Permissions.IN,
ImmutableList.of(getPersonalBlessingString(), sOpts.getCloudBlessingString()));
}
@@ -123,28 +130,41 @@
*
* @param opts initial options
*/
- public static void init(Options opts) throws VError {
- System.loadLibrary("syncbase");
- sOpts = opts;
- io.v.syncbase.internal.Service.Init(sOpts.rootDir, sOpts.testLogin);
- if (isLoggedIn()) {
- io.v.syncbase.internal.Service.Serve();
+ public static void init(Options opts) throws SyncbaseException {
+ try {
+
+ System.loadLibrary("syncbase");
+ sOpts = opts;
+ io.v.syncbase.internal.Service.Init(sOpts.rootDir, sOpts.testLogin);
+ if (isLoggedIn()) {
+ io.v.syncbase.internal.Service.Serve();
+ }
+
+ } catch (VError e) {
+ chainThrow("initializing Syncbase", e);
}
}
/**
* Returns a Database object. Return null if the user is not currently logged in.
*/
- public static Database database() throws VError {
- if (!isLoggedIn()) {
- return null;
- }
- if (sDatabase != null) {
- // TODO(sadovsky): Check that opts matches original opts (sOpts)?
+ public static Database database() throws SyncbaseException {
+ try {
+
+ if (!isLoggedIn()) {
+ return null;
+ }
+ if (sDatabase != null) {
+ // TODO(sadovsky): Check that opts matches original opts (sOpts)?
+ return sDatabase;
+ }
+ sDatabase = new Database(Service.database(DB_NAME));
return sDatabase;
+
+ } catch (VError e) {
+ chainThrow("getting the database", e);
+ throw new AssertionError("never happens");
}
- sDatabase = new Database(Service.database(DB_NAME));
- return sDatabase;
}
/**
@@ -247,8 +267,8 @@
cb.onSuccess();
}
});
- } catch (VError vError) {
- cb.onError(vError);
+ } catch (Throwable e) {
+ cb.onError(e);
}
}
}).start();
@@ -398,11 +418,18 @@
return parts[parts.length - 1];
}
- static String getPersonalBlessingString() throws VError {
- return Blessings.UserBlessingFromContext();
+ static String getPersonalBlessingString() throws SyncbaseException {
+ try {
+
+ return Blessings.UserBlessingFromContext();
+
+ } catch(VError e) {
+ chainThrow("getting certificates from context", e);
+ throw new AssertionError("never happens");
+ }
}
- static Permissions defaultDatabasePerms() throws VError {
+ static Permissions defaultDatabasePerms() throws SyncbaseException {
// TODO(sadovsky): Revisit these default perms, which were copied from the Todos app.
Map anyone = ImmutableMap.of(Permissions.IN, ImmutableList.of("..."));
Map selfAndCloud = selfAndCloud();
@@ -413,7 +440,7 @@
Permissions.Tags.ADMIN, selfAndCloud));
}
- static Permissions defaultCollectionPerms() throws VError {
+ static Permissions defaultCollectionPerms() throws SyncbaseException {
// TODO(sadovsky): Revisit these default perms, which were copied from the Todos app.
Map selfAndCloud = selfAndCloud();
return new Permissions(ImmutableMap.of(
@@ -422,7 +449,7 @@
Permissions.Tags.ADMIN, selfAndCloud));
}
- static Permissions defaultSyncgroupPerms() throws VError {
+ static Permissions defaultSyncgroupPerms() throws SyncbaseException {
// TODO(sadovsky): Revisit these default perms, which were copied from the Todos app.
Map selfAndCloud = selfAndCloud();
return new Permissions(ImmutableMap.of(
diff --git a/syncbase/src/main/java/io/v/syncbase/Syncgroup.java b/syncbase/src/main/java/io/v/syncbase/Syncgroup.java
index d764077..ef41110 100644
--- a/syncbase/src/main/java/io/v/syncbase/Syncgroup.java
+++ b/syncbase/src/main/java/io/v/syncbase/Syncgroup.java
@@ -14,6 +14,9 @@
import io.v.syncbase.core.SyncgroupSpec;
import io.v.syncbase.core.VError;
import io.v.syncbase.core.VersionedSyncgroupSpec;
+import io.v.syncbase.exception.SyncbaseException;
+
+import static io.v.syncbase.exception.Exceptions.chainThrow;
/**
* Represents a set of collections, synced amongst a set of users.
@@ -28,7 +31,7 @@
mDatabase = database;
}
- void createIfMissing(List<Collection> collections) throws VError {
+ void createIfMissing(List<Collection> collections) throws SyncbaseException {
ArrayList<io.v.syncbase.core.Id> ids = new ArrayList<>();
for (Collection cx : collections) {
ids.add(cx.getId().toCoreId());
@@ -52,7 +55,7 @@
// configuration, e.g., the specified collections? instead of returning early.
return;
}
- throw vError;
+ chainThrow("creating syncgroup for collections", vError);
}
}
@@ -73,17 +76,24 @@
* Returns the {@code AccessList} for this syncgroup.
* Throws if the current user is not an admin of the syncgroup or its collection.
*/
- public AccessList getAccessList() throws VError {
+ public AccessList getAccessList() throws SyncbaseException {
// TODO(alexfandrianto): Rework for advanced users.
// We will not ask for the syncgroup spec. Instead, we will rely on the collection of this
// syncgroup to have the correct permissions. There is an issue with not being able to
// determine READ vs READ_WRITE from just the syncgroup spec because the write tag is only
// available on the collection. This workaround will assume only a single collection per
// syncgroup, which is why it might not succeed for advanced users.
- Id cId = new Id(mCoreSyncgroup.getSpec().syncgroupSpec.collections.get(0));
- return mDatabase.getCollection(cId).getAccessList();
+ try {
- // return new AccessList(mCoreSyncgroup.getSpec().syncgroupSpec.permissions);
+ Id cId = new Id(mCoreSyncgroup.getSpec().syncgroupSpec.collections.get(0));
+ return mDatabase.getCollection(cId).getAccessList();
+
+ // return new AccessList(mCoreSyncgroup.getSpec().syncgroupSpec.permissions);
+
+ } catch (VError e) {
+ chainThrow("getting access list of syncgroup", getId(), e);
+ throw new AssertionError("never happens");
+ }
}
/**
@@ -103,7 +113,7 @@
* FOR ADVANCED USERS. Adds the given users to the syncgroup, with the specified access level.
*/
public void inviteUsers(List<User> users, AccessList.AccessLevel level,
- UpdateAccessListOptions opts) throws VError {
+ UpdateAccessListOptions opts) throws SyncbaseException {
AccessList delta = new AccessList();
for (User u : users) {
delta.setAccessLevel(u, level);
@@ -114,21 +124,22 @@
/**
* Adds the given users to the syncgroup, with the specified access level.
*/
- public void inviteUsers(List<User> users, AccessList.AccessLevel level) throws VError {
+ public void inviteUsers(List<User> users, AccessList.AccessLevel level)
+ throws SyncbaseException {
inviteUsers(users, level, new UpdateAccessListOptions());
}
/**
* Adds the given user to the syncgroup, with the specified access level.
*/
- public void inviteUser(User user, AccessList.AccessLevel level) throws VError {
+ public void inviteUser(User user, AccessList.AccessLevel level) throws SyncbaseException {
inviteUsers(Collections.singletonList(user), level);
}
/**
* FOR ADVANCED USERS. Removes the given users from the syncgroup.
*/
- public void ejectUsers(List<User> users, UpdateAccessListOptions opts) throws VError {
+ public void ejectUsers(List<User> users, UpdateAccessListOptions opts) throws SyncbaseException {
AccessList delta = new AccessList();
for (User u : users) {
delta.removeAccessLevel(u);
@@ -139,14 +150,14 @@
/**
* Removes the given users from the syncgroup.
*/
- public void ejectUsers(List<User> users) throws VError {
+ public void ejectUsers(List<User> users) throws SyncbaseException {
ejectUsers(users, new UpdateAccessListOptions());
}
/**
* Removes the given user from the syncgroup.
*/
- public void ejectUser(User user) throws VError {
+ public void ejectUser(User user) throws SyncbaseException {
ejectUsers(Collections.singletonList(user));
}
@@ -154,32 +165,29 @@
* FOR ADVANCED USERS. Applies {@code delta} to the {@code AccessList}.
*/
public void updateAccessList(final AccessList delta, UpdateAccessListOptions opts)
- throws VError {
- // TODO(sadovsky): Make it so SyncgroupSpec can be updated as part of a batch?
- VersionedSyncgroupSpec versionedSyncgroupSpec;
+ throws SyncbaseException {
try {
- versionedSyncgroupSpec = mCoreSyncgroup.getSpec();
- } catch (VError vError) {
- throw new RuntimeException("getSpec failed", vError);
- }
- versionedSyncgroupSpec.syncgroupSpec.permissions = AccessList.applyDeltaForSyncgroup(
- versionedSyncgroupSpec.syncgroupSpec.permissions, delta);
- mCoreSyncgroup.setSpec(versionedSyncgroupSpec);
- // TODO(sadovsky): There's a race here - it's possible for a collection to get destroyed
- // after getSpec() but before db.getCollection().
- final List<io.v.syncbase.core.Id> collectionsIds =
- versionedSyncgroupSpec.syncgroupSpec.collections;
- mDatabase.runInBatch(new Database.BatchOperation() {
- @Override
- public void run(BatchDatabase db) {
- for (io.v.syncbase.core.Id id : collectionsIds) {
- try {
+
+ // TODO(sadovsky): Make it so SyncgroupSpec can be updated as part of a batch?
+ VersionedSyncgroupSpec versionedSyncgroupSpec = mCoreSyncgroup.getSpec();
+ versionedSyncgroupSpec.syncgroupSpec.permissions = AccessList.applyDeltaForSyncgroup(
+ versionedSyncgroupSpec.syncgroupSpec.permissions, delta);
+ mCoreSyncgroup.setSpec(versionedSyncgroupSpec);
+ // TODO(sadovsky): There's a race here - it's possible for a collection to get destroyed
+ // after getSpec() but before db.getCollection().
+ final List<io.v.syncbase.core.Id> collectionsIds =
+ versionedSyncgroupSpec.syncgroupSpec.collections;
+ mDatabase.runInBatch(new Database.BatchOperation() {
+ @Override
+ public void run(BatchDatabase db) throws SyncbaseException {
+ for (io.v.syncbase.core.Id id : collectionsIds) {
db.getCollection(new Id(id)).updateAccessList(delta);
- } catch (VError vError) {
- throw new RuntimeException("getCollection failed", vError);
}
}
- }
- });
+ });
+
+ } catch (VError e) {
+ chainThrow("updating access list of syncgroup", getId(), e);
+ }
}
}
diff --git a/syncbase/src/main/java/io/v/syncbase/WatchChange.java b/syncbase/src/main/java/io/v/syncbase/WatchChange.java
index 5160de1..b8ab288 100644
--- a/syncbase/src/main/java/io/v/syncbase/WatchChange.java
+++ b/syncbase/src/main/java/io/v/syncbase/WatchChange.java
@@ -4,10 +4,13 @@
package io.v.syncbase;
-import io.v.syncbase.core.VError;
+import io.v.syncbase.exception.SyncbaseException;
+import io.v.syncbase.exception.SyncbaseInternalException;
import io.v.v23.verror.VException;
import io.v.v23.vom.VomUtil;
+import static io.v.syncbase.exception.Exceptions.chainThrow;
+
/**
* Describes a change to a database.
*/
@@ -32,21 +35,20 @@
private final boolean mContinued;
WatchChange(io.v.syncbase.core.WatchChange change) {
+ // TODO(eobrain): use switch statements below
if (change.entityType == io.v.syncbase.core.WatchChange.EntityType.COLLECTION) {
mEntityType = EntityType.COLLECTION;
} else if (change.entityType == io.v.syncbase.core.WatchChange.EntityType.ROW) {
mEntityType = EntityType.ROW;
} else {
- // TODO(razvanm): Throw an exception after https://v.io/c/23420 is submitted.
- throw new RuntimeException("Unknown EntityType: " + change.entityType);
+ throw new SyncbaseInternalException("Unknown EntityType: " + change.entityType);
}
if (change.changeType == io.v.syncbase.core.WatchChange.ChangeType.PUT) {
mChangeType = ChangeType.PUT;
} else if (change.changeType == io.v.syncbase.core.WatchChange.ChangeType.DELETE) {
mChangeType = ChangeType.DELETE;
} else {
- // TODO(razvanm): Throw an SyncbaseException after https://v.io/c/23420 is submitted.
- throw new RuntimeException("Unknown ChangeType: " + change.changeType);
+ throw new SyncbaseInternalException("Unknown ChangeType: " + change.changeType);
}
mCollectionId = new Id(change.collection);
mRowKey = change.row;
@@ -72,11 +74,12 @@
return mRowKey;
}
- public <T> T getValue(Class<T> cls) throws VError {
+ public <T> T getValue(Class<T> cls) throws SyncbaseException {
try {
return (T) VomUtil.decode(mValue, cls);
} catch (VException e) {
- throw new VError(e);
+ chainThrow("getting value from a WatchChange of collection", mCollectionId, e);
+ throw new AssertionError("never happens");
}
}
diff --git a/syncbase/src/main/java/io/v/syncbase/core/VError.java b/syncbase/src/main/java/io/v/syncbase/core/VError.java
index 37d6938..8020ee1 100644
--- a/syncbase/src/main/java/io/v/syncbase/core/VError.java
+++ b/syncbase/src/main/java/io/v/syncbase/core/VError.java
@@ -4,10 +4,6 @@
package io.v.syncbase.core;
-import java.util.Arrays;
-
-import io.v.v23.verror.VException;
-
public class VError extends Exception {
public static final String EXIST = "v.io/v23/verror.Exist";
public static final String NO_EXIST = "v.io/v23/verror.NoExist";
@@ -22,13 +18,6 @@
private VError() {
}
- public VError(VException e) {
- this.id = e.getID();
- this.actionCode = e.getAction().getValue();
- this.message = e.getMessage();
- this.stack = Arrays.toString(e.getStackTrace());
- }
-
public String toString() {
return String.format("{\n id: \"%s\"\n actionCode: %d\n message: \"%s\"\n stack: \"%s\"}",
id, actionCode, message, stack);
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/Exceptions.java b/syncbase/src/main/java/io/v/syncbase/exception/Exceptions.java
new file mode 100644
index 0000000..c01821c
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/Exceptions.java
@@ -0,0 +1,150 @@
+// 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.exception;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.CancellationException;
+
+import io.v.syncbase.Id;
+import io.v.syncbase.core.VError;
+import io.v.v23.verror.VException;
+import io.v.v23.verror.VException.ActionCode;
+
+import static io.v.v23.verror.VException.ActionCode.fromValue;
+
+/**
+ * Utility for exception chaining.
+ */
+public final class Exceptions {
+
+ private Exceptions() {
+ }
+
+ private static String baseName(String v23ErrorId) {
+ String[] tokens = v23ErrorId.split("\\.");
+ int n = tokens.length;
+ if (n < 1) {
+ return v23ErrorId;
+ }
+ return tokens[n - 1];
+ }
+
+ private static void chainThrow(String message, String goErrorId, ActionCode action, Exception
+ cause)
+ throws SyncbaseException {
+ if (goErrorId == null) {
+ goErrorId = "null";
+ }
+ String fullMessage = message + ": " + baseName(goErrorId);
+ switch (goErrorId) {
+ case "v.io/v23/verror.NotImplemented":
+ throw new UnsupportedOperationException(fullMessage, cause);
+
+ case "v.io/v23/verror.EndOfFile":
+ throw new SyncbaseEndOfFileException(fullMessage, cause);
+
+ case "v.io/v23/verror.BadArg":
+ case "v.io/v23/services/syncbase.InvalidName":
+ case "v.io/v23/services/syncbase.NotBoundToBatch":
+ case "v.io/v23/services/syncbase.ReadOnlyBatch":
+ throw new IllegalArgumentException(fullMessage, cause);
+
+ case "v.io/v23/verror.Exist":
+ case "v.io/v23/services/syncbase.NotInDevMode":
+ case "v.io/v23/services/syncbase.BlobNotCommitted":
+ case "v.io/v23/services/syncbase.InvalidPermissionsChange":
+ case "v.io/v23/verror.Aborted":
+ throw new IllegalStateException(fullMessage, cause);
+
+ case "v.io/v23/verror.NoExist":
+ throw withCause(new NoSuchElementException(fullMessage), cause);
+
+ case "v.io/v23/verror.Unknown":
+ case "v.io/v23/verror.Internal":
+ case "v.io/v23/verror.BadState":
+ case "v.io/v23/verror.UnknownMethod":
+ case "v.io/v23/verror.UnknownSuffix":
+ case "v.io/v23/verror.BadProtocol":
+ case "v.io/v23/services/syncbase.BadExecStreamHeader":
+ throw new SyncbaseInternalException(fullMessage, cause);
+
+ case "v.io/v23/verror.NoServers":
+ case "v.io/v23/services/syncbase.SyncgroupJoinFailed":
+ throw new SyncbaseNoServersException(fullMessage, cause);
+
+ case "v.io/v23/verror.Canceled":
+ throw withCause(new CancellationException(fullMessage), cause);
+
+ case "v.io/v23/verror.Timeout":
+ throw new SyncbaseRetryBackoffException(fullMessage, cause);
+
+ case "v.io/v23/services/syncbase.CorruptDatabase":
+ throw new SyncbaseRestartException(fullMessage, cause);
+
+ case "v.io/v23/verror.BadVersion":
+ case "v.io/v23/services/syncbase.ConcurrentBatch":
+ case "v.io/v23/services/syncbase.UnknownBatch":
+ throw new SyncbaseRaceException(fullMessage, cause);
+
+ case "v.io/v23/verror.NoAccess":
+ case "v.io/v23/verror.NotTrusted":
+ case "v.io/v23/verror.NoExistOrNoAccess":
+ case "v.io/v23/services/syncbase.UnauthorizedCreateId":
+ case "v.io/v23/services/syncbase.InferAppBlessingFailed":
+ case "v.io/v23/services/syncbase.InferUserBlessingFailed":
+ case "v.io/v23/services/syncbase.InferDefaultPermsFailed":
+ throw new SyncbaseSecurityException(fullMessage, cause);
+
+ default:
+ String fullerMessage = fullMessage + " (unexpected error ID " + goErrorId + ")";
+ // See https://godoc.org/v.io/v23/verror#ActionCode
+ switch (action) {
+ case RETRY_REFETCH:
+ throw new SyncbaseRetryRefetchException(fullerMessage, cause);
+ case RETRY_BACKOFF:
+ throw new SyncbaseRetryBackoffException(fullerMessage, cause);
+ case RETRY_CONNECTION:
+ throw new SyncbaseRetryConnectionException(fullerMessage, cause);
+ case NO_RETRY:
+ default:
+ throw new SyncbaseInternalException(fullerMessage, cause);
+ }
+ }
+ }
+
+ private static void chainThrow(String javaMessage, String goMessage, String v23ErrorId,
+ ActionCode action, Exception cause) throws SyncbaseException {
+ chainThrow("while " + javaMessage + " got error " + goMessage, v23ErrorId, action, cause);
+ }
+
+ public static void chainThrow(String javaMessage, VError cause) throws SyncbaseException {
+ ActionCode action = fromValue((int) cause.actionCode);
+ chainThrow(javaMessage, cause.message, cause.id, action, cause);
+ }
+
+ public static void chainThrow(String javaMessage, VException cause) throws SyncbaseException {
+ chainThrow(javaMessage, cause.getMessage(), cause.getID(), cause.getAction(),
+ cause);
+ }
+
+ public static void chainThrow(String doing, Id where, VError cause) throws SyncbaseException {
+ chainThrow(doing + " " + where.getName(), cause);
+ }
+
+ public static void chainThrow(String doing, Id where, VException cause) throws
+ SyncbaseException {
+ chainThrow(doing + " " + where.getName(), cause);
+ }
+
+ public static void chainThrow(String doing, io.v.syncbase.core.Id where, VError cause) throws
+ SyncbaseException {
+ chainThrow(doing + " " + where.name, cause);
+ }
+
+ private static <T extends Exception> T withCause(T e, Exception cause) {
+ e.initCause(cause);
+ return e;
+ }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/README.md b/syncbase/src/main/java/io/v/syncbase/exception/README.md
new file mode 100644
index 0000000..b47dc62
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/README.md
@@ -0,0 +1,49 @@
+# Syncbase Exceptions
+
+The methods in the `io.v.syncbase` package can throw the following exceptions:
+
+* java.lang.Exception
+ * *io.v.syncbase.exception.SyncbaseException*
+ * *io.v.syncbase.exception.**SyncbaseEndOfFileException***
+ * *io.v.syncbase.exception.**SyncbaseNoServersException***
+ * *io.v.syncbase.exception.**SyncbaseRaceException***
+ * *io.v.syncbase.exception.**SyncbaseRestartException***
+ * *io.v.syncbase.exception.**SyncbaseRetryBackoffException***
+ * *io.v.syncbase.exception.**SyncbaseRetryFetchException***
+ * *io.v.syncbase.exception.**SyncbaseSecurityException***
+ * java.lang.RuntimeException
+ * *io.v.syncbase.exception.**SyncbaseInternalException***
+ * java.lang.**IllegalArgumentException**
+ * java.lang.**IllegalStateException**
+ * java.util.concurrent.**CancellationException**
+ * java.util.**NoSuchElementException**
+ * java.lang.**UnsupportedOperationException**
+
+
+## Exceptions Detected in Java
+
+Some particular method in the `io.v.syncbase` package can throw these exceptions:
+
+| Method Thrown From | Exception |
+| ------------------ |-----------|
+| `Syncgroup.getAccessList` or `Collection.getAccessList` (various inconsistencies in permissions) or<br/> `Syncbase.login` (unsupported authentication provider) | SyncbaseSecurityException |
+| `BatchDatabase.collection` (opts.withoutSyncgroup parameter is false),<br/> `Collection.getSyncgroup` (Collection is in a batch),<br/> `Database.syncgroup` (no collections or collection without creator), or<br/> `Id.decode` (invalid encoded ID) | IllegalArgumentException |
+
+## Exceptions Caused by Underlying Vanadium Errors
+
+Any method in the `io.v.syncbase` package can throw any of these exceptions:
+
+| Vanadium Error | Exception |
+| -------------- | --------- |
+| EndOfFile | SyncbaseEndOfFileException |
+| NoServers SyncgroupJoinFailed | SyncbaseNoServersException |
+| BadVersion ConcurrentBatch UnknownBatch | SyncbaseRaceException |
+| CorruptDatabase | SyncbaseRestartException |
+| Timeout | SyncbaseRetryBackoffException |
+| NoAccess NotTrusted NoExistOrNoAccess UnauthorizedCreateId InferAppBlessingFailed InferUserBlessingFailed InferDefaultPermsFailed | SyncbaseSecurityException |
+| Unknown Internal BadState UnknownMethod UnknownSuffix BadProtocol BadExecStreamHeader | SyncbaseInternalException |
+| BadArg InvalidName NotBoundToBatch ReadOnlyBatch | IllegalArgumentException |
+| Exist NotInDevMode BlobNotCommitted InvalidPermissionsChange Aborted | IllegalStateException |
+| Canceled | CancellationException |
+| NoExist | NoSuchElementException |
+| NotImplemented | UnsupportedOperationException |
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseEndOfFileException.java b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseEndOfFileException.java
new file mode 100644
index 0000000..10c474f
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseEndOfFileException.java
@@ -0,0 +1,14 @@
+// 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.exception;
+
+/**
+ * Thrown in response to Vanadium error: EndOfFile.
+ */
+public class SyncbaseEndOfFileException extends SyncbaseException {
+ SyncbaseEndOfFileException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseException.java b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseException.java
new file mode 100644
index 0000000..7a4038f
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseException.java
@@ -0,0 +1,14 @@
+// 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.exception;
+
+/**
+ * A Syncbase error that the client code might want to handle, depending on subclass thrown.
+ */
+public abstract class SyncbaseException extends Exception {
+ SyncbaseException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseInternalException.java b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseInternalException.java
new file mode 100644
index 0000000..af90546
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseInternalException.java
@@ -0,0 +1,18 @@
+// 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.exception;
+
+/**
+ * An internal error within Syncbase.
+ */
+public class SyncbaseInternalException extends RuntimeException {
+ public SyncbaseInternalException(String message) {
+ super(message);
+ }
+
+ SyncbaseInternalException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseNoServersException.java b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseNoServersException.java
new file mode 100644
index 0000000..f47b9ae
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseNoServersException.java
@@ -0,0 +1,14 @@
+// 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.exception;
+
+/**
+ * Thrown in response to Vanadium errors: NoServers or SyncgroupJoinFailed.
+ */
+public class SyncbaseNoServersException extends SyncbaseException {
+ SyncbaseNoServersException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRaceException.java b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRaceException.java
new file mode 100644
index 0000000..18b48d3
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRaceException.java
@@ -0,0 +1,15 @@
+// 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.exception;
+
+/**
+ * Thrown in response to Vanadium errors: BadVersion, ConcurrentBatch, or UnknownBatch. The failing
+ * method may possibly succeed later if retried.
+ */
+public class SyncbaseRaceException extends SyncbaseException {
+ SyncbaseRaceException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRestartException.java b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRestartException.java
new file mode 100644
index 0000000..ad672bf
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRestartException.java
@@ -0,0 +1,15 @@
+// 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.exception;
+
+/**
+ * Thrown in response to Vanadium error: CorruptDatabase. The client code may choose to
+ * re-initialize the local Syncbase, deleting local information.
+ */
+public class SyncbaseRestartException extends SyncbaseException {
+ SyncbaseRestartException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRetryBackoffException.java b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRetryBackoffException.java
new file mode 100644
index 0000000..db1090f
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRetryBackoffException.java
@@ -0,0 +1,15 @@
+// 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.exception;
+
+/**
+ * Thrown in response to Vanadium errors: Timeout, or some other errors that have the RetryBackoff
+ * action code. The failing method may possibly succeed if retried after a delay.
+ */
+public class SyncbaseRetryBackoffException extends SyncbaseException {
+ SyncbaseRetryBackoffException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRetryConnectionException.java b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRetryConnectionException.java
new file mode 100644
index 0000000..ada78b8
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRetryConnectionException.java
@@ -0,0 +1,15 @@
+// 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.exception;
+
+/**
+ * Thrown in response to some other errors that have the RetryConnection action code. The failing
+ * method may possibly succeed if retried after the connection is re-established.
+ */
+public class SyncbaseRetryConnectionException extends SyncbaseException {
+ SyncbaseRetryConnectionException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRetryRefetchException.java b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRetryRefetchException.java
new file mode 100644
index 0000000..77cd961
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseRetryRefetchException.java
@@ -0,0 +1,15 @@
+// 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.exception;
+
+/**
+ * Thrown in response to some other errors that have the RetryRefetch action code. The failing
+ * method may possibly succeed if retried after fetching update versions of the parameters.
+ */
+public class SyncbaseRetryRefetchException extends SyncbaseException {
+ SyncbaseRetryRefetchException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseSecurityException.java b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseSecurityException.java
new file mode 100644
index 0000000..f4a0984
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/exception/SyncbaseSecurityException.java
@@ -0,0 +1,16 @@
+// 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.exception;
+
+/**
+ * Thrown in response to various inconsistencies in permissions, or unsupported authentication
+ * provider, or Vanadium errors: NoAccess, NotTrusted, NoExistOrNoAccess, UnauthorizedCreateId,
+ * InferAppBlessingFailed, InferUserBlessingFailed, or InferDefaultPermsFailed.
+ */
+public class SyncbaseSecurityException extends SyncbaseException {
+ SyncbaseSecurityException(String message, Exception cause) {
+ super(message, cause);
+ }
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/BatchDatabaseTest.java b/syncbase/src/test/java/io/v/syncbase/BatchDatabaseTest.java
new file mode 100644
index 0000000..c598c1a
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/BatchDatabaseTest.java
@@ -0,0 +1,50 @@
+// 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 org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+
+import io.v.syncbase.DatabaseHandle.CollectionOptions;
+import io.v.syncbase.exception.SyncbaseException;
+
+import static io.v.syncbase.TestUtil.setUpDatabase;
+
+public class BatchDatabaseTest {
+ @Rule
+ public final TemporaryFolder folder = new TemporaryFolder();
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public void setUp() throws IOException, InterruptedException, ExecutionException,
+ SyncbaseException {
+ setUpDatabase(folder.newFolder());
+ }
+
+ @After
+ public void tearDown() {
+ Syncbase.shutdown();
+ }
+
+ @Test
+ public void attemptCreateSyncgroupWithinBatch() throws SyncbaseException,
+ ExecutionException, InterruptedException {
+ BatchDatabase batch = Syncbase.database().beginBatch(new Database.BatchOptions());
+
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Cannot create syncgroup in a batch");
+
+ batch.collection("aCollection", new CollectionOptions());
+ }
+
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/CollectionTest.java b/syncbase/src/test/java/io/v/syncbase/CollectionTest.java
new file mode 100644
index 0000000..76312e7
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/CollectionTest.java
@@ -0,0 +1,69 @@
+// 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 org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+import io.v.syncbase.DatabaseHandle.CollectionOptions;
+import io.v.syncbase.exception.SyncbaseException;
+
+import static io.v.syncbase.TestUtil.setUpDatabase;
+
+public class CollectionTest {
+ @Rule
+ public final TemporaryFolder folder = new TemporaryFolder();
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ private static final Executor sameThreadExecutor = new Executor() {
+ public void execute(Runnable runnable) {
+ runnable.run();
+ }
+ };
+
+ @Before
+ public void setUp() throws IOException, InterruptedException, ExecutionException,
+ SyncbaseException {
+ setUpDatabase(folder.newFolder());
+ }
+
+ @After
+ public void tearDown() {
+ Syncbase.shutdown();
+ }
+
+ @Test
+ public void badName() throws SyncbaseException,
+ ExecutionException, InterruptedException {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("invalid name");
+
+ // Create with invalid name
+ Syncbase.database().collection("name with spaces", new CollectionOptions());
+ }
+
+ @Test
+ public void attemptGetSyncgroupWithinBatch() throws SyncbaseException,
+ ExecutionException, InterruptedException {
+ BatchDatabase batch = Syncbase.database().beginBatch(new Database.BatchOptions());
+
+ // Create collection without a syncgroup
+ Collection collection = batch.collection("collectionName",
+ new CollectionOptions().setWithoutSyncgroup(true));
+
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Must not call getSyncgroup within batch");
+ collection.getSyncgroup();
+ }
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/DatabaseTest.java b/syncbase/src/test/java/io/v/syncbase/DatabaseTest.java
new file mode 100644
index 0000000..ef21baa
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/DatabaseTest.java
@@ -0,0 +1,48 @@
+// 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 org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+
+import io.v.syncbase.exception.SyncbaseException;
+
+import static io.v.syncbase.TestUtil.setUpDatabase;
+
+public class DatabaseTest {
+ @Rule
+ public final TemporaryFolder folder = new TemporaryFolder();
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public void setUp() throws Exception {
+ setUpDatabase(folder.newFolder());
+ }
+
+ @After
+ public void tearDown() {
+ Syncbase.shutdown();
+ }
+
+ @Test
+ public void noCollections() throws IOException, SyncbaseException,
+ ExecutionException, InterruptedException {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("No collections");
+
+ // Try to create a syncgroup with no collections
+ Syncbase.database().syncgroup("aSyncgroup", new ArrayList<Collection>());
+ }
+
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/ExceptionsTest.java b/syncbase/src/test/java/io/v/syncbase/ExceptionsTest.java
new file mode 100644
index 0000000..44c4949
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/ExceptionsTest.java
@@ -0,0 +1,164 @@
+// 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 junit.framework.Assert;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.NoSuchElementException;
+
+import io.v.syncbase.core.VError;
+import io.v.syncbase.exception.SyncbaseException;
+import io.v.syncbase.exception.SyncbaseRaceException;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.v.syncbase.exception.Exceptions.chainThrow;
+
+public class ExceptionsTest {
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ /**
+ * Make VError, using reflection to call private constructor.
+ */
+ private static VError newVError(String message, String goErrorId) throws
+ IllegalAccessException, InvocationTargetException,
+ InstantiationException, NoSuchMethodException {
+ Constructor<VError> constructor = VError.class.getDeclaredConstructor();
+ constructor.setAccessible(true);
+ VError e = constructor.newInstance();
+ e.message = message;
+ e.id = goErrorId;
+ return e;
+ }
+
+ @Test
+ public void exist() throws SyncbaseException, IllegalAccessException, InstantiationException,
+ NoSuchMethodException, InvocationTargetException {
+ VError cause = newVError("bar", VError.EXIST);
+
+ thrown.expect(IllegalStateException.class);
+ thrown.expectMessage("while fooing got error bar: Exist");
+
+ chainThrow("fooing", cause);
+ }
+
+ @Test
+ public void noExist() throws SyncbaseException, IllegalAccessException, InstantiationException,
+ NoSuchMethodException, InvocationTargetException {
+ VError cause = newVError("bar", VError.NO_EXIST);
+
+ thrown.expect(NoSuchElementException.class);
+ thrown.expectMessage("while fooing got error bar: NoExist");
+
+ chainThrow("fooing", cause);
+ }
+
+ @Test
+ public void concurrentBatch() throws SyncbaseException, IllegalAccessException,
+ InstantiationException,
+ NoSuchMethodException, InvocationTargetException {
+ VError cause = newVError("bar", VError.SYNCBASE_CONCURRENT_BATCH);
+
+ thrown.expect(SyncbaseRaceException.class);
+ thrown.expectMessage("while fooing got error bar: ConcurrentBatch");
+
+ chainThrow("fooing", cause);
+ }
+
+ @Test
+ public void allv23ErrorsFromGo() throws InvocationTargetException, NoSuchMethodException,
+ InstantiationException, IllegalAccessException {
+ // See https://godoc.org/v.io/v23/verror#pkg-variables
+ String[] v23Errors = {
+ "Unknown",
+ "Internal",
+ "NotImplemented",
+ "EndOfFile",
+ "BadArg",
+ "BadState",
+ "BadVersion",
+ "Exist",
+ "NoExist",
+ "UnknownMethod",
+ "UnknownSuffix",
+ "NoExistOrNoAccess",
+ "NoServers",
+ "NoAccess",
+ "NotTrusted",
+ "Aborted",
+ "BadProtocol",
+ "Canceled",
+ "Timeout",
+ };
+ for (String error : v23Errors) {
+ String errorId = "v.io/v23/verror." + error;
+
+ VError cause = newVError("bar", errorId);
+ try {
+ chainThrow("fooing", cause);
+ Assert.fail("Expected assertion to be thrown");
+ } catch (SyncbaseException e) {
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(VError.class);
+ assertThat(e.getMessage().contains(": " + error));
+ assertThat(e.getMessage()).doesNotContain("unexpected error ID");
+ } catch (RuntimeException e) {
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(VError.class);
+ assertThat(e.getMessage().contains(": " + error));
+ assertThat(e.getMessage()).doesNotContain("unexpected error ID");
+ }
+ }
+ }
+
+ @Test
+ public void allSyncbaseErrorsFromGo() throws InvocationTargetException, NoSuchMethodException,
+ InstantiationException, IllegalAccessException {
+ // See https://godoc.org/v.io/v23/verror#pkg-variables
+ String[] syncbaseErrors = {
+ "NotInDevMode",
+ "InvalidName",
+ "CorruptDatabase",
+ "UnknownBatch",
+ "NotBoundToBatch",
+ "ReadOnlyBatch",
+ "ConcurrentBatch",
+ "BlobNotCommitted",
+ "SyncgroupJoinFailed",
+ "BadExecStreamHeader",
+ "InvalidPermissionsChange",
+ "UnauthorizedCreateId",
+ "InferAppBlessingFailed",
+ "InferUserBlessingFailed",
+ "InferDefaultPermsFailed",
+ };
+ for (String error : syncbaseErrors) {
+ String errorId = "v.io/v23/services/syncbase." + error;
+
+ VError cause = newVError("bar", errorId);
+ try {
+ chainThrow("fooing", cause);
+ Assert.fail("Expected assertion to be thrown");
+ } catch (SyncbaseException e) {
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(VError.class);
+ assertThat(e.getMessage().contains(": " + error));
+ assertThat(e.getMessage()).doesNotContain("unexpected error ID");
+ } catch (RuntimeException e) {
+ assertThat(e.getCause()).isNotNull();
+ assertThat(e.getCause()).isInstanceOf(VError.class);
+ assertThat(e.getMessage().contains(": " + error));
+ assertThat(e.getMessage()).doesNotContain("unexpected error ID");
+ }
+ }
+ }
+
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/IdTest.java b/syncbase/src/test/java/io/v/syncbase/IdTest.java
new file mode 100644
index 0000000..f801aa1
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/IdTest.java
@@ -0,0 +1,35 @@
+// 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 org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static com.google.common.truth.Truth.assertThat;
+
+public class IdTest {
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void goodEncoding() {
+ Id original = new Id("aBlessing", "aName");
+ String encoded = original.encode();
+
+ Id decoded = Id.decode(encoded);
+
+ assertThat(decoded.getBlessing()).isEqualTo("aBlessing");
+ assertThat(decoded.getName()).isEqualTo("aName");
+ }
+
+ @Test
+ public void invalidEncoding() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Invalid encoded ID");
+
+ Id.decode("not valid");
+ }
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/SyncbaseTest.java b/syncbase/src/test/java/io/v/syncbase/SyncbaseTest.java
index 72a2431..82f410e 100644
--- a/syncbase/src/test/java/io/v/syncbase/SyncbaseTest.java
+++ b/syncbase/src/test/java/io/v/syncbase/SyncbaseTest.java
@@ -11,6 +11,7 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import java.util.ArrayList;
@@ -21,32 +22,29 @@
import io.v.syncbase.core.Permissions;
import io.v.syncbase.core.VError;
+import io.v.syncbase.exception.SyncbaseException;
import static com.google.common.truth.Truth.assertThat;
+import static io.v.syncbase.TestUtil.createDatabase;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class SyncbaseTest {
@Rule
public final TemporaryFolder folder = new TemporaryFolder();
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
// To run these tests from Android Studio, add the following VM option to the default JUnit
// build configuration, via Run > Edit Configurations... > Defaults > JUnit > VM options:
// -Djava.library.path=/Users/sadovsky/vanadium/release/java/syncbase/build/libs
@Before
public void setUp() throws Exception {
- Syncbase.Options opts = new Syncbase.Options();
- opts.rootDir = folder.newFolder().getAbsolutePath();
- opts.disableUserdataSyncgroup = true;
- opts.disableSyncgroupPublishing = true;
- opts.testLogin = true;
- // Unlike Android apps, the test doesn't have a looper/handler, so use a different executor.
- opts.callbackExecutor = Executors.newCachedThreadPool();
- Syncbase.init(opts);
+ TestUtil.setUpSyncbase(folder.newFolder());
}
@After
@@ -54,26 +52,7 @@
Syncbase.shutdown();
}
- private Database createDatabase() throws Exception {
- final SettableFuture<Void> future = SettableFuture.create();
-
- Syncbase.login("", "", new Syncbase.LoginCallback() {
- @Override
- public void onSuccess() {
- future.set(null);
- }
-
- @Override
- public void onError(Throwable e) {
- future.setException(e);
- }
- });
-
- future.get(5, TimeUnit.SECONDS);
- return Syncbase.database();
- }
-
- private static Iterable<Id> getCollectionIds(Database db) throws VError {
+ private static Iterable<Id> getCollectionIds(Database db) throws SyncbaseException {
List<Id> res = new ArrayList<>();
for (Iterator<Collection> it = db.getCollections(); it.hasNext(); ) {
res.add(it.next().getId());
@@ -81,7 +60,7 @@
return res;
}
- private static Iterable<Id> getSyncgroupIds(Database db) throws VError {
+ private static Iterable<Id> getSyncgroupIds(Database db) throws SyncbaseException {
List<Id> res = new ArrayList<>();
for (Iterator<Syncgroup> it = db.getSyncgroups(); it.hasNext(); ) {
res.add(it.next().getId());
@@ -252,9 +231,9 @@
DatabaseHandle.CollectionOptions opts = new DatabaseHandle.CollectionOptions()
.setWithoutSyncgroup(true);
db.collection("c", opts).put("foo", 10);
- } catch (VError vError) {
- vError.printStackTrace();
- fail(vError.toString());
+ } catch (SyncbaseException e) {
+ e.printStackTrace();
+ fail(e.toString());
}
}
});
@@ -337,4 +316,20 @@
// This is supposed to fail.
}
}
-}
\ No newline at end of file
+
+ @Test
+ public void unsupportedAuthenticationProvider() {
+ thrown.expect(IllegalArgumentException.class);
+ thrown.expectMessage("Unsupported provider: bogusProvider");
+
+ Syncbase.login("", "bogusProvider", new Syncbase.LoginCallback() {
+ @Override
+ public void onSuccess() {
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ }
+ });
+ }
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/TestUtil.java b/syncbase/src/test/java/io/v/syncbase/TestUtil.java
new file mode 100644
index 0000000..37ec0ce
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/TestUtil.java
@@ -0,0 +1,58 @@
+// 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 com.google.common.util.concurrent.SettableFuture;
+
+import java.io.File;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+import io.v.syncbase.exception.SyncbaseException;
+
+class TestUtil {
+ private static final Executor sameThreadExecutor = new Executor() {
+ public void execute(Runnable runnable) {
+ runnable.run();
+ }
+ };
+
+ static Database createDatabase() throws ExecutionException, InterruptedException,
+ SyncbaseException {
+ final SettableFuture<Void> future = SettableFuture.create();
+
+ Syncbase.login("", "", new Syncbase.LoginCallback() {
+ @Override
+ public void onSuccess() {
+ future.set(null);
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ future.setException(e);
+ }
+ });
+
+ future.get();
+ return Syncbase.database();
+ }
+
+ static void setUpSyncbase(File folder) throws SyncbaseException, ExecutionException,
+ InterruptedException {
+ Syncbase.Options opts = new Syncbase.Options();
+ opts.rootDir = folder.getAbsolutePath();
+ opts.disableUserdataSyncgroup = true;
+ opts.disableSyncgroupPublishing = true;
+ opts.testLogin = true;
+ opts.callbackExecutor = sameThreadExecutor;
+ Syncbase.init(opts);
+ }
+
+ static void setUpDatabase(File folder) throws SyncbaseException, ExecutionException,
+ InterruptedException {
+ setUpSyncbase(folder);
+ createDatabase();
+ }
+}