Java: first syncbase client implementation
Few notes:
- for now, we add the "Type" parameter in get() and set()s,
as we don't yet have the VOM support we need.
- tests will be done in a separate CL.
Change-Id: I37a5f9ff36504e6721bc7624aec7158d3d24e65e
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/Syncbase.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/Syncbase.java
new file mode 100644
index 0000000..80af359
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/Syncbase.java
@@ -0,0 +1,21 @@
+// 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.syncbase.v23.services.syncbase;
+
+/**
+ * Various syncbase utility methods.
+ */
+public class Syncbase {
+ /**
+ * Returns a new handle to a syncbase service running at the given name.
+ *
+ * @param fullName full (i.e., object) name of the syncbase service
+ */
+ public static SyncbaseService newService(String fullName) {
+ return new SyncbaseServiceImpl(fullName);
+ }
+
+ private Syncbase() {}
+}
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseAppImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseAppImpl.java
new file mode 100644
index 0000000..50f428e
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseAppImpl.java
@@ -0,0 +1,69 @@
+// 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.syncbase.v23.services.syncbase;
+
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+
+import io.v.impl.google.naming.NamingUtil;
+import io.v.syncbase.v23.services.syncbase.nosql.Database;
+import io.v.syncbase.v23.services.syncbase.nosql.NoSql;
+import io.v.syncbase.v23.services.syncbase.nosql.Schema;
+import io.v.syncbase.v23.services.syncbase.util.Util;
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.services.permissions.ObjectClient.GetPermissionsOut;
+import io.v.v23.verror.VException;
+
+class SyncbaseAppImpl implements SyncbaseApp {
+ private final String fullName;
+ private final String name;
+ private final AppClient client;
+
+ SyncbaseAppImpl(String parentFullName, String relativeName) {
+ this.fullName = NamingUtil.join(parentFullName, relativeName);
+ this.name = relativeName;
+ this.client = AppClientFactory.getAppClient(this.fullName);
+ }
+
+ @Override
+ public String name() {
+ return this.name;
+ }
+ @Override
+ public String fullName() {
+ return this.fullName;
+ }
+ @Override
+ public boolean exists(VContext ctx) throws VException {
+ return this.client.exists(ctx);
+ }
+ @Override
+ public Database getNoSqlDatabase(String relativeName, Schema schema) {
+ return NoSql.newDatabase(this.fullName, relativeName, schema);
+ }
+ @Override
+ public String[] listDatabases(VContext ctx) throws VException {
+ return Util.list(ctx, this.fullName);
+ }
+ @Override
+ public void create(VContext ctx, Permissions perms) throws VException {
+ this.client.create(ctx, perms);
+ }
+ @Override
+ public void delete(VContext ctx) throws VException {
+ this.client.delete(ctx);
+ }
+ @Override
+ public void setPermissions(VContext ctx, Permissions perms, String version) throws VException {
+ this.client.setPermissions(ctx, perms, version);
+ }
+ @Override
+ public Map<String, Permissions> getPermissions(VContext ctx) throws VException {
+ GetPermissionsOut perms = this.client.getPermissions(ctx);
+ return ImmutableMap.of(perms.version, perms.perms);
+ }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseServiceImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseServiceImpl.java
new file mode 100644
index 0000000..32bfb44
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/SyncbaseServiceImpl.java
@@ -0,0 +1,47 @@
+// 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.syncbase.v23.services.syncbase;
+
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+
+import io.v.syncbase.v23.services.syncbase.util.Util;
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.services.permissions.ObjectClient.GetPermissionsOut;
+import io.v.v23.verror.VException;
+
+class SyncbaseServiceImpl implements SyncbaseService {
+ private final String fullName;
+ private final ServiceClient client;
+
+ SyncbaseServiceImpl(String fullName) {
+ this.fullName = fullName;
+ this.client = ServiceClientFactory.getServiceClient(fullName);
+ }
+
+ @Override
+ public String fullName() {
+ return this.fullName;
+ }
+ @Override
+ public SyncbaseApp getApp(String relativeName) {
+ return new SyncbaseAppImpl(this.fullName, relativeName);
+ }
+ @Override
+ public String[] listApps(VContext ctx) throws VException {
+ return Util.list(ctx, this.fullName);
+ }
+ @Override
+ public void setPermissions(VContext ctx, Permissions perms, String version) throws VException {
+ this.client.setPermissions(ctx, perms, version);
+ }
+ @Override
+ public Map<String, Permissions> getPermissions(VContext ctx) throws VException {
+ GetPermissionsOut perms = this.client.getPermissions(ctx);
+ return ImmutableMap.of(perms.version, perms.perms);
+ }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Database.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Database.java
index d1f2e2f..ec2aa98 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Database.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Database.java
@@ -56,10 +56,11 @@
/**
* Deletes the table with the given name.
*
- * @param ctx Vanadium context
- * @throws VException if the table couldn't be deleted
+ * @param ctx Vanadium context
+ * @param relativeName relative name of the table; must not contain {@code /}
+ * @throws VException if the table couldn't be deleted
*/
- void deleteTable(VContext ctx) throws VException;
+ void deleteTable(VContext ctx, String relativeName) throws VException;
/**
* Creates a new "batch", i.e., a handle to a set of reads and writes to the database that
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseCore.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseCore.java
index bf11ea6..f4ad6fe 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseCore.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseCore.java
@@ -23,9 +23,6 @@
/**
* Returns the table with the given name.
- * <p>
- * Note that this table may not yet exist and can be created using the {@link Table#create}
- * method.)
*
* @param relativeName name of the table; must not contain slashes
*/
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseImpl.java
new file mode 100644
index 0000000..9e185de
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/DatabaseImpl.java
@@ -0,0 +1,200 @@
+// 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.syncbase.v23.services.syncbase.nosql;
+
+import java.io.EOFException;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import com.google.common.collect.ImmutableMap;
+
+import io.v.impl.google.naming.NamingUtil;
+import io.v.syncbase.v23.services.syncbase.nosql.Schema;
+import io.v.syncbase.v23.services.syncbase.util.Util;
+import io.v.v23.context.CancelableVContext;
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.services.permissions.ObjectClient.GetPermissionsOut;
+import io.v.v23.vdl.TypedClientStream;
+import io.v.v23.vdl.VdlAny;
+import io.v.v23.vdl.VdlOptional;
+import io.v.v23.verror.VException;
+
+class DatabaseImpl implements Database, BatchDatabase {
+ private final String parentFullName;
+ private final String fullName;
+ private final String name;
+ private final Schema schema;
+ private final DatabaseClient client;
+
+ DatabaseImpl(String parentFullName, String relativeName, Schema schema) {
+ this.parentFullName = parentFullName;
+ this.fullName = NamingUtil.join(parentFullName, relativeName);
+ this.name = relativeName;
+ this.schema = schema;
+ this.client = DatabaseClientFactory.getDatabaseClient(this.fullName);
+ }
+
+ // Implements DatabaseCore interface.
+ @Override
+ public String name() {
+ return this.name;
+ }
+ @Override
+ public String fullName() {
+ return this.fullName;
+ }
+ @Override
+ public Table getTable(String relativeName) {
+ return new TableImpl(this.fullName, relativeName, getSchemaVersion());
+ }
+ @Override
+ public String[] listTables(VContext ctx) throws VException {
+ return Util.list(ctx, this.fullName);
+ }
+ @Override
+ public ResultStream exec(VContext ctx, String query) throws VException {
+ CancelableVContext ctxC = ctx.withCancel();
+ TypedClientStream<Void, List<VdlAny>, Void> stream =
+ this.client.exec(ctxC, getSchemaVersion(), query);
+
+ // The first row contains column names, pull them off the stream.
+ List<VdlAny> row = null;
+ try {
+ row = stream.recv();
+ } catch (EOFException e) {
+ throw new VException("Got empty exec() stream for query: " + query);
+ }
+ String[] columnNames = new String[row.size()];
+ for (int i = 0; i < row.size(); ++i) {
+ Serializable elem = row.get(i).getElem();
+ if (elem instanceof String) {
+ columnNames[i] = (String) elem;
+ } else {
+ throw new VException("Expected first row in exec() stream to contain column " +
+ "names (of type String), got type: " + elem.getClass());
+ }
+ }
+ return new ResultStreamImpl(ctxC, stream, columnNames);
+ }
+
+ // Implements AccessController interface.
+ @Override
+ public void setPermissions(VContext ctx, Permissions perms, String version) throws VException {
+ this.client.setPermissions(ctx, perms, version);
+ }
+ @Override
+ public Map<String, Permissions> getPermissions(VContext ctx) throws VException {
+ GetPermissionsOut perms = this.client.getPermissions(ctx);
+ return ImmutableMap.of(perms.version, perms.perms);
+ }
+
+ // Implements Database interface.
+ @Override
+ public boolean exists(VContext ctx) throws VException {
+ return this.client.exists(ctx, getSchemaVersion());
+ }
+ @Override
+ public void create(VContext ctx, Permissions perms) throws VException {
+ SchemaMetadata metadata = this.schema != null ? this.schema.getMetadata() : null;
+ this.client.create(ctx, VdlOptional.of(metadata), perms);
+ }
+ @Override
+ public void delete(VContext ctx) throws VException {
+ this.client.delete(ctx, getSchemaVersion());
+ }
+ @Override
+ public void createTable(VContext ctx, String relativeName, Permissions perms)
+ throws VException {
+ String tableFullName = NamingUtil.join(this.fullName, relativeName);
+ TableClient table = TableClientFactory.getTableClient(tableFullName);
+ table.create(ctx, getSchemaVersion(), perms);
+ }
+ @Override
+ public void deleteTable(VContext ctx, String relativeName) throws VException {
+ String tableFullName = NamingUtil.join(this.fullName, relativeName);
+ TableClient table = TableClientFactory.getTableClient(tableFullName);
+ table.delete(ctx, getSchemaVersion());
+ }
+ @Override
+ public BatchDatabase beginBatch(VContext ctx, BatchOptions opts) throws VException {
+ String relativeName = this.client.beginBatch(ctx, getSchemaVersion(), opts);
+ return new DatabaseImpl(this.parentFullName, relativeName, this.schema);
+ }
+ @Override
+ public SyncGroup getSyncGroup(String name) {
+ return new SyncGroupImpl(this.fullName, name);
+ }
+ @Override
+ public String[] listSyncGroupNames(VContext ctx) throws VException {
+ List<String> names = this.client.getSyncGroupNames(ctx);
+ return names.toArray(new String[names.size()]);
+ }
+ @Override
+ public boolean upgradeIfOutdated(VContext ctx) throws VException {
+ if (this.schema == null) {
+ throw new VException(io.v.v23.flow.Errors.BAD_STATE, ctx,
+ "Schema or SchemaMetadata cannot be null. A valid Schema needs to be used " +
+ "when creating a database handle.");
+ }
+ if (this.schema.getMetadata().getVersion() < 0) {
+ throw new VException(io.v.v23.flow.Errors.BAD_STATE, ctx,
+ "Schema version cannot be less than zero.");
+ }
+ SchemaManager schemaManager = new SchemaManager(this.fullName);
+ SchemaMetadata currMetadata = null;
+ try {
+ currMetadata = schemaManager.getSchemaMetadata(ctx);
+ } catch (VException eGet) {
+ // If the client app did not set a schema as part of Database.Create(),
+ // getSchemaMetadata() will return Errors.NO_EXIST. If so we set the schema
+ // here.
+ if (!eGet.getID().equals(io.v.v23.verror.Errors.NO_EXIST)) {
+ throw eGet;
+ }
+ try {
+ schemaManager.setSchemaMetadata(ctx, this.schema.getMetadata());
+ } catch (VException eSet) {
+ if (!eSet.getID().equals(io.v.v23.verror.Errors.NO_EXIST)) {
+ throw eSet;
+ }
+ }
+ return false;
+ }
+ if (currMetadata.getVersion() >= this.schema.getMetadata().getVersion()) {
+ return false;
+ }
+ // Call the Upgrader provided by the app to upgrade the schema.
+ //
+ // TODO(jlodhia): disable sync before running Upgrader and reenable
+ // once Upgrader is finished.
+ //
+ // TODO(jlodhia): prevent other processes (local/remote) from accessing
+ // the database while upgrade is in progress.
+ this.schema.getUpgrader().run(this,
+ currMetadata.getVersion(), this.schema.getMetadata().getVersion());
+
+ // Update the schema metadata in db to the latest version.
+ schemaManager.setSchemaMetadata(ctx, this.schema.getMetadata());
+ return true;
+ }
+
+ // Implements BatchDatabase.
+ @Override
+ public void commit(VContext ctx) throws VException {
+ this.client.commit(ctx, getSchemaVersion());
+ }
+ @Override
+ public void abort(VContext ctx) throws VException {
+ this.client.abort(ctx, getSchemaVersion());
+ }
+
+ private int getSchemaVersion() {
+ if (this.schema == null) {
+ return -1;
+ }
+ return this.schema.getMetadata().getVersion();
+ }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/NoSql.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/NoSql.java
index 5fd7c62..4540823 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/NoSql.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/NoSql.java
@@ -11,24 +11,28 @@
* Various utility methods for the NoSql database.
*/
public class NoSql {
+ public static Database newDatabase(String parentFullName, String relativeName, Schema schema) {
+ return new DatabaseImpl(parentFullName, relativeName, schema);
+ }
+
/**
* Creates a {@link RowRange} representing a single row.
*/
- public static RowRange singleRowRange(String row) {
+ public static RowRange newSingleRowRange(String row) {
return new RowRangeImpl(row);
}
/**
* Creates a {@link RowRange} represented by the provided {@code [start, limit)} parameters.
*/
- public static RowRange range(String start, String limit) {
+ public static RowRange newRowRange(String start, String limit) {
return new RowRangeImpl(start, limit);
}
/**
* Creates {@link PrefixRange} with the provided prefix.
*/
- public static PrefixRange prefix(String prefix) {
+ public static PrefixRange newPrefixRange(String prefix) {
return new PrefixRangeImpl(prefix);
}
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRange.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRange.java
index d550ab4..4745827 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRange.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRange.java
@@ -11,5 +11,5 @@
/**
* Returns the prefix shared by all the keys in the range.
*/
- String prefix();
+ String getPrefix();
}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRangeImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRangeImpl.java
index 00e5170..80b9546 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRangeImpl.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/PrefixRangeImpl.java
@@ -14,17 +14,17 @@
}
@Override
- public String start() {
+ public String getStart() {
return Util.prefixRangeStart(prefix);
}
@Override
- public String limit() {
+ public String getLimit() {
return Util.prefixRangeLimit(prefix);
}
@Override
- public String prefix() {
+ public String getPrefix() {
return this.prefix;
}
}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStream.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStream.java
index 8ff2771..e106e36 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStream.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStream.java
@@ -1,29 +1,39 @@
// 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.syncbase.v23.services.syncbase.nosql;
-import io.v.v23.vdl.VdlValue;
+import io.v.v23.vdl.VdlAny;
import io.v.v23.verror.VException;
/**
* An interface for iterating through rows resulting from a
* {@link DatabaseCore#exec DatabaseCore.exec()}.
+ *
+ * The {@link java.util.Iterator} returned by the {@link java.lang.Iterable#iterator iterator}:
+ * <p><ul>
+ * <li>can be created <strong>only</strong> once,
+ * <li>does not support {@link java.util.Iterator#remove remove}</li>, and
+ * <li>may throw {@link RuntimeException} if the underlying syncbase read throws
+ * a {@link VException}. The {@link RuntimeException#getCause cause} of the
+ * {@link RuntimeException} will be the said {@link VException}.</li>
+ * </ul>
*/
-public interface ResultStream extends Stream {
+public interface ResultStream extends Iterable<VdlAny[]> {
/**
- * Returns an array of column names that matched the query. The number of values returned
- * by each call to {@link #result} will match the size of this array.
+ * Returns an array of column names that matched the query. The size of the {@link VdlAny}
+ * array returned in every iteration will match the size of this array.
*/
String[] columnNames();
/**
- * Returns the result that was staged by {@link #advance}.
+ * Notifies the stream provider that it can stop producing elements. The client must call
+ * {@link #cancel} if it does not iterate through all the elements.
* <p>
- * This method does not block.
- *
- * @throws VException if the value could not be decoded or if {@link #advance} returned
- * {@code false} or was not called at all
+ * This method is idempotent and can be called concurrently with a thread that is iterating.
+ * <p>
+ * This method causes the iterator to (gracefully) terminate early.
*/
- VdlValue[] result() throws VException;
+ void cancel() throws VException;
}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStreamImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStreamImpl.java
new file mode 100644
index 0000000..bb33b9f
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ResultStreamImpl.java
@@ -0,0 +1,69 @@
+// 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.syncbase.v23.services.syncbase.nosql;
+
+import com.google.common.collect.AbstractIterator;
+
+import java.io.EOFException;
+import java.util.Iterator;
+import java.util.List;
+
+import io.v.v23.context.CancelableVContext;
+import io.v.v23.vdl.TypedClientStream;
+import io.v.v23.vdl.VdlAny;
+import io.v.v23.verror.VException;
+
+class ResultStreamImpl implements ResultStream {
+ private final CancelableVContext ctxC;
+ private final TypedClientStream<Void, List<VdlAny>, Void> stream;
+ private final String[] columnNames;
+ private volatile boolean isCanceled;
+ private volatile boolean isCreated;
+
+ ResultStreamImpl(CancelableVContext ctxC, TypedClientStream<Void, List<VdlAny>, Void> stream,
+ String[] columnNames) {
+ this.ctxC = ctxC;
+ this.stream = stream;
+ this.columnNames = columnNames;
+ this.isCanceled = this.isCreated = false;
+ }
+ // Implements Iterable.
+ @Override
+ public synchronized Iterator<VdlAny[]> iterator() {
+ if (this.isCreated) {
+ throw new RuntimeException("Can only create one ResultStream iterator.");
+ }
+ this.isCreated = true;
+ return new AbstractIterator<VdlAny[]>() {
+ @Override
+ protected VdlAny[] computeNext() {
+ synchronized (ResultStreamImpl.this) {
+ if (ResultStreamImpl.this.isCanceled) { // client canceled the stream
+ return endOfData();
+ }
+ try {
+ List<VdlAny> result = ResultStreamImpl.this.stream.recv();
+ return result.toArray(new VdlAny[result.size()]);
+ } catch (EOFException e) { // legitimate end of stream
+ return endOfData();
+ } catch (VException e) {
+ throw new RuntimeException("Error retrieving next stream element.", e);
+ }
+ }
+ }
+ };
+ }
+
+ // Implements ResultStream.
+ @Override
+ public String[] columnNames() {
+ return this.columnNames;
+ }
+ @Override
+ public synchronized void cancel() throws VException {
+ this.isCanceled = true;
+ this.stream.finish();
+ }
+}
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Row.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Row.java
index ba953bd..4ba462f 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Row.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Row.java
@@ -3,6 +3,8 @@
// license that can be found in the LICENSE file.
package io.v.syncbase.v23.services.syncbase.nosql;
+import java.lang.reflect.Type;
+
import io.v.v23.context.VContext;
import io.v.v23.verror.VException;
@@ -43,7 +45,7 @@
* @param ctx Vanadium context
* @throws VException if the value couldn't be retrieved
*/
- Object get(VContext ctx) throws VException;
+ Object get(VContext ctx, Type type) throws VException;
/**
* Writes the given value for this row.
@@ -52,5 +54,5 @@
* @param value value to write
* @throws VException if the value couldn't be written
*/
- void put(VContext ctx, Object value) throws VException;
+ void put(VContext ctx, Object value, Type type) throws VException;
}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowImpl.java
new file mode 100644
index 0000000..0451dd8
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowImpl.java
@@ -0,0 +1,53 @@
+// 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.syncbase.v23.services.syncbase.nosql;
+
+import java.lang.reflect.Type;
+
+import io.v.impl.google.naming.NamingUtil;
+import io.v.syncbase.v23.services.syncbase.util.Util;
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+import io.v.v23.vom.VomUtil;
+
+class RowImpl implements Row {
+ private final String fullName;
+ private final String key;
+ private final int schemaVersion;
+ private final RowClient client;
+
+ RowImpl(String parentFullName, String key, int schemaVersion) {
+ this.fullName = NamingUtil.join(parentFullName, key);
+ this.key = key;
+ this.schemaVersion = schemaVersion;
+ this.client = RowClientFactory.getRowClient(this.fullName);
+ }
+ @Override
+ public String key() {
+ return this.key;
+ }
+ @Override
+ public String fullName() {
+ return this.fullName;
+ }
+ @Override
+ public boolean exists(VContext ctx) throws VException {
+ return this.client.exists(ctx, this.schemaVersion);
+ }
+ @Override
+ public void delete(VContext ctx) throws VException {
+ this.client.delete(ctx, this.schemaVersion);
+ }
+ @Override
+ public Object get(VContext ctx, Type type) throws VException {
+ byte[] data = this.client.get(ctx, this.schemaVersion);
+ return VomUtil.decode(data, type);
+ }
+ @Override
+ public void put(VContext ctx, Object value, Type type) throws VException {
+ byte[] data = VomUtil.encode(value, type);
+ this.client.put(ctx, this.schemaVersion, data);
+ }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRange.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRange.java
index e1f9503..7de840d 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRange.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRange.java
@@ -12,10 +12,10 @@
/**
* Returns the key that marks the start of the row range.
*/
- public String start();
+ public String getStart();
/**
* Returns the key that marks the limit of the row range.
*/
- public String limit();
+ public String getLimit();
}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRangeImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRangeImpl.java
index 7363fc1..3359cf3 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRangeImpl.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/RowRangeImpl.java
@@ -17,8 +17,8 @@
}
@Override
- public String start() { return this.start; }
+ public String getStart() { return this.start; }
@Override
- public String limit() { return this.limit; }
+ public String getLimit() { return this.limit; }
}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStream.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStream.java
index 54bcee8..8a47fbe 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStream.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStream.java
@@ -8,24 +8,24 @@
/**
* An interface for iterating through a collection of key/value pairs (obtained via
* {@link Table#scan Table.scan()}).
+ *
+ * The {@link java.util.Iterator} returned by the {@link java.lang.Iterable#iterator iterator}:
+ * <p><ul>
+ * <li>can be created <strong>only</strong> once,
+ * <li>does not support {@link java.util.Iterator#remove remove}</li>, and
+ * <li>may throw {@link RuntimeException} if the underlying syncbase read throws
+ * a {@link VException}. The {@link RuntimeException#getCause cause} of the
+ * {@link RuntimeException} will be the said {@link VException}.</li>
+ * </ul>
*/
-public interface ScanStream extends Stream {
+public interface ScanStream extends Iterable<KeyValue> {
/**
- * Returns the key of the element that was staged by {@link #advance}.
+ * Notifies the stream provider that it can stop producing elements. The client must call
+ * {@link #cancel} if it does not iterate through all the elements.
* <p>
- * This method does not block.
- *
- * @throws VException if {@link #advance} returned {@code false} or was not called at all
- */
- String key() throws VException;
-
- /**
- * Returns the value of the element that was staged by {@link #advance}.
+ * This method is idempotent and can be called concurrently with a thread that is iterating.
* <p>
- * This method does not block.
- *
- * @throws VException if the value could not be decoded or if {@link #advance} returned
- * {@code false} or was not called at all
+ * This method causes the iterator to (gracefully) terminate early.
*/
- Object value() throws VException;
+ void cancel() throws VException;
}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStreamImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStreamImpl.java
new file mode 100644
index 0000000..77e7f93
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/ScanStreamImpl.java
@@ -0,0 +1,59 @@
+// 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.syncbase.v23.services.syncbase.nosql;
+
+import com.google.common.collect.AbstractIterator;
+
+import java.io.EOFException;
+import java.util.Iterator;
+
+import io.v.v23.context.CancelableVContext;
+import io.v.v23.vdl.TypedClientStream;
+import io.v.v23.verror.VException;
+
+class ScanStreamImpl implements ScanStream {
+ private final CancelableVContext ctxC;
+ private final TypedClientStream<Void, KeyValue, Void> stream;
+ private volatile boolean isCanceled;
+ private volatile boolean isCreated;
+
+ ScanStreamImpl(CancelableVContext ctxC, TypedClientStream<Void, KeyValue, Void> stream) {
+ this.ctxC = ctxC;
+ this.stream = stream;
+ this.isCanceled = this.isCreated = false;
+ }
+ // Implements Iterable.
+ @Override
+ public synchronized Iterator<KeyValue> iterator() {
+ if (this.isCreated) {
+ throw new RuntimeException("Can only create one ScanStream iterator.");
+ }
+ this.isCreated = true;
+ return new AbstractIterator<KeyValue>() {
+ @Override
+ protected KeyValue computeNext() {
+ synchronized (ScanStreamImpl.this) {
+ if (ScanStreamImpl.this.isCanceled) { // client canceled the stream
+ return endOfData();
+ }
+ try {
+ return ScanStreamImpl.this.stream.recv();
+ } catch (EOFException e) { // legitimate end of stream
+ return endOfData();
+ } catch (VException e) {
+ throw new RuntimeException("Error retrieving next stream element.", e);
+ }
+ }
+ }
+ };
+ }
+
+ // Implements ScanStream.
+ @Override
+ public synchronized void cancel() throws VException {
+ this.isCanceled = true;
+ this.stream.finish();
+ }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Schema.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Schema.java
index 9c036c7..9665483 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Schema.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Schema.java
@@ -31,11 +31,11 @@
/**
* Returns the metadata related to this schema.
*/
- public SchemaMetadata metadata() { return this.metadata; }
+ public SchemaMetadata getMetadata() { return this.metadata; }
/**
* Returns the upgrade logic used for upgrading the schema when an app's schema version differs
* from the database's schema version.
*/
- public SchemaUpgrader upgrader() { return this.upgrader; }
+ public SchemaUpgrader getUpgrader() { return this.upgrader; }
}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SchemaManager.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SchemaManager.java
new file mode 100644
index 0000000..ed4590f
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SchemaManager.java
@@ -0,0 +1,24 @@
+// 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.syncbase.v23.services.syncbase.nosql;
+
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+
+class SchemaManager {
+ private final DatabaseClient client;
+
+ SchemaManager(String dbFullName) {
+ this.client = DatabaseClientFactory.getDatabaseClient(dbFullName);
+ }
+
+ SchemaMetadata getSchemaMetadata(VContext ctx) throws VException {
+ return this.client.getSchemaMetadata(ctx);
+ }
+
+ void setSchemaMetadata(VContext ctx, SchemaMetadata metadata) throws VException {
+ this.client.setSchemaMetadata(ctx, metadata);
+ }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Stream.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Stream.java
deleted file mode 100644
index 15aa392..0000000
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Stream.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// 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.syncbase.v23.services.syncbase.nosql;
-
-import io.v.v23.verror.VException;
-
-/**
- * An interface for iterating through a collection of elements.
- */
-public interface Stream {
- /**
- * Stages an element so that the client can retrieve it.
- * <p>
- * Returns {@code true} iff there is an element to retrieve.
- * <p>
- * The client must call {@link #advance} before retrieving the element.
- * <p>
- * The client must call {@link #cancel} if it does not iterate through all elements
- * (i.e. until {@link #advance} returns {@code false}).
- * <p>
- * This method may block if an element is not immediately available.
- *
- * @return {@code true} iff there is an element to retrieve
- * @throws VException if there was an error advancing the stream
- */
- boolean advance() throws VException;
-
- /**
- * Notifies the stream provider that it can stop producing elements. The client must call
- * {@link #cancel} if it does not iterate through all elements (i.e. until {@link #advance})
- * returns {@code false}).
- * <p>
- * This method is idempotent and can be called concurrently with a thread that is
- * iterating via {@link #advance}.
- * <p>
- * This method causes {@link #advance} to subsequently return {@code false}.
- * <p>
- * This method does not block.
- */
- void cancel();
-}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroup.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroup.java
index 7c3071a..b9dfc90 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroup.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroup.java
@@ -13,15 +13,6 @@
*/
public interface SyncGroup {
/**
- * Returns {@code true} iff this sync group exists and the caller has at least {@code read}
- * permissions on it.
- *
- * @param ctx Vanadium context
- * @throws VException if the sync group's existence couldn't be determined
- */
- boolean exists(VContext ctx) throws VException;
-
- /**
* Creates a new sync group with the given spec. Requires:
* <p>
* <ul>
@@ -105,7 +96,7 @@
*
* @param ctx Vanadium context
* @return sync group specification along with its version number. The returned
- * map is guaranteed to be non-{@code null} and contain exactly one member
+ * map is guaranteed to be non-{@code null} and contain exactly one element
* @throws VException if the sync group specification couldn't be retrieved
*/
Map<String, SyncGroupSpec> getSpec(VContext ctx) throws VException;
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroupImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroupImpl.java
new file mode 100644
index 0000000..373e820
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/SyncGroupImpl.java
@@ -0,0 +1,59 @@
+// 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.syncbase.v23.services.syncbase.nosql;
+
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+
+import io.v.syncbase.v23.services.syncbase.nosql.Schema;
+import io.v.syncbase.v23.services.syncbase.nosql.SyncGroupManagerClient.GetSyncGroupSpecOut;
+import io.v.v23.context.VContext;
+import io.v.v23.verror.VException;
+
+class SyncGroupImpl implements SyncGroup {
+ private final String name;
+ private final String dbFullName;
+ private final DatabaseClient dbClient;
+
+ SyncGroupImpl(String dbFullName, String name) {
+ this.name = name;
+ this.dbFullName = dbFullName;
+ this.dbClient = DatabaseClientFactory.getDatabaseClient(dbFullName);
+ }
+ @Override
+ public void create(VContext ctx, SyncGroupSpec spec, SyncGroupMemberInfo info) throws VException {
+ this.dbClient.createSyncGroup(ctx, this.name, spec, info);
+ }
+ @Override
+ public SyncGroupSpec join(VContext ctx, SyncGroupMemberInfo info) throws VException {
+ return this.dbClient.joinSyncGroup(ctx, this.name, info);
+ }
+ @Override
+ public void leave(VContext ctx) throws VException {
+ this.dbClient.leaveSyncGroup(ctx, this.name);
+ }
+ @Override
+ public void destroy(VContext ctx) throws VException {
+ this.dbClient.destroySyncGroup(ctx, this.name);
+ }
+ @Override
+ public void eject(VContext ctx, String member) throws VException {
+ this.dbClient.ejectFromSyncGroup(ctx, this.name, member);
+ }
+ @Override
+ public Map<String, SyncGroupSpec> getSpec(VContext ctx) throws VException {
+ GetSyncGroupSpecOut spec = this.dbClient.getSyncGroupSpec(ctx, this.name);
+ return ImmutableMap.of(spec.version, spec.spec);
+ }
+ @Override
+ public void setSpec(VContext ctx, SyncGroupSpec spec, String version) throws VException {
+ this.dbClient.setSyncGroupSpec(ctx, this.name, spec, version);
+ }
+ @Override
+ public Map<String, SyncGroupMemberInfo> getMembers(VContext ctx) throws VException {
+ return this.dbClient.getSyncGroupMembers(ctx, this.name);
+ }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Table.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Table.java
index ee89215..ee598a6 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Table.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/Table.java
@@ -4,6 +4,8 @@
package io.v.syncbase.v23.services.syncbase.nosql;
+import java.lang.reflect.Type;
+
import io.v.v23.context.VContext;
import io.v.v23.security.access.Permissions;
import io.v.v23.verror.VException;
@@ -43,20 +45,22 @@
*
* @param ctx Vanadium context
* @param key the primary key for a row
+ * @param type type of the value to be returned (needed for de-serialization)
* @throws VException if the value couldn't be retrieved or if its class doesn't match the
* provided class
*/
- Object get(VContext ctx, String key) throws VException;
+ Object get(VContext ctx, String key, Type type) throws VException;
/**
* Writes the value to the table under the provided primary key.
*
* @param ctx Vanadium context
* @param key primary key under which the value is to be written
+ * @param type type of the value to be returned (needed for serialization)
* @param value value to be written
* @throws VException if the value couldn't be written
*/
- void put(VContext ctx, String key, Object value) throws VException;
+ void put(VContext ctx, String key, Object value, Type type) throws VException;
/**
* Deletes all rows in the given half-open range {@code [start, limit)}. If {@code limit} is
@@ -76,14 +80,17 @@
* reads from a consistent snapshot taken at the time of the method and will not reflect
* subsequent writes to keys not yet reached by the stream.
*
- * See helpers {@link NoSql#prefix NoSql.prefix()}, {@link NoSql#range NoSql.range()},
- * and {@link NoSql#singleRowRange NoSql.singleRowRange()}.
+ * See helpers {@link NoSql#newPrefixRange NoSql.newPrefixRange()},
+ * {@link NoSql#newRowRange NoSql.newRowRange()},
+ * and {@link NoSql#newSingleRowRange NoSql.newSingleRowRange()}.
*
- * @param ctx Vanadium context
- * @param range range of rows to be read
- * @return a {@link ScanStream} used for iterating over the snapshot of the provided rows
+ * @param ctx Vanadium context
+ * @param range range of rows to be read
+ * @return a {@link ScanStream} used for iterating over the snapshot of the
+ * provided rows
+ * @throws VException if the scan stream couldn't be created
*/
- ScanStream scan(VContext ctx, RowRange range);
+ ScanStream scan(VContext ctx, RowRange range) throws VException;
/**
* Returns an array of {@link PrefixPermissions} (i.e., {@code (prefix, perms)} pairs) for
@@ -92,6 +99,9 @@
* The array is sorted from longest prefix to shortest, so element zero is the one that
* applies to the row with the given key. The last element is always the prefix {@code ""}
* which represents the table's permissions -- the array will always have at least one element.
+ * <p>
+ * TODO(spetrovic): Make a change to VDL so that PrefixPermissions.prefix decodes into a
+ * PrefixRange.
*
* @param ctx Vanadium context
* @param key key of the row whose permission prefixes are to be retrieved
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/TableImpl.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/TableImpl.java
new file mode 100644
index 0000000..a52eb6e
--- /dev/null
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/nosql/TableImpl.java
@@ -0,0 +1,83 @@
+// 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.syncbase.v23.services.syncbase.nosql;
+
+import java.lang.reflect.Type;
+import java.util.List;
+
+import io.v.impl.google.naming.NamingUtil;
+import io.v.syncbase.v23.services.syncbase.util.Util;
+import io.v.v23.context.CancelableVContext;
+import io.v.v23.context.VContext;
+import io.v.v23.security.access.Permissions;
+import io.v.v23.vdl.TypedClientStream;
+import io.v.v23.verror.VException;
+
+class TableImpl implements Table {
+ private final String fullName;
+ private final String name;
+ private final int schemaVersion;
+ private final TableClient client;
+
+ TableImpl(String parentFullName, String relativeName, int schemaVersion) {
+ this.fullName = NamingUtil.join(parentFullName, relativeName);
+ this.name = relativeName;
+ this.schemaVersion = schemaVersion;
+ this.client = TableClientFactory.getTableClient(this.fullName);
+ }
+
+ @Override
+ public String name() {
+ return this.name;
+ }
+ @Override
+ public String fullName() {
+ return this.fullName;
+ }
+ @Override
+ public boolean exists(VContext ctx) throws VException {
+ return this.client.exists(ctx, this.schemaVersion);
+ }
+ @Override
+ public Row getRow(String key) {
+ return new RowImpl(this.fullName, key, this.schemaVersion);
+ }
+ @Override
+ public Object get(VContext ctx, String key, Type type) throws VException {
+ return getRow(key).get(ctx, type);
+ }
+ @Override
+ public void put(VContext ctx, String key, Object value, Type type) throws VException {
+ getRow(key).put(ctx, value, type);
+ }
+ @Override
+ public void delete(VContext ctx, RowRange range) throws VException {
+ this.client.deleteRowRange(ctx, this.schemaVersion,
+ Util.getBytes(range.getStart()), Util.getBytes(range.getLimit()));
+ }
+ @Override
+ public ScanStream scan(VContext ctx, RowRange range) throws VException {
+ CancelableVContext ctxC = ctx.withCancel();
+ TypedClientStream<Void, KeyValue, Void> stream = this.client.scan(ctxC, this.schemaVersion,
+ Util.getBytes(range.getStart()), Util.getBytes(range.getLimit()));
+ return new ScanStreamImpl(ctxC, stream);
+ }
+ @Override
+ public PrefixPermissions[] getPermissions(VContext ctx, String key) throws VException {
+ List<PrefixPermissions> perms = this.client.getPermissions(ctx, this.schemaVersion, key);
+ return perms.toArray(new PrefixPermissions[perms.size()]);
+ }
+ @Override
+ public void setPermissions(VContext ctx, PrefixRange prefix, Permissions perms)
+ throws VException {
+ this.client.setPermissions(ctx, this.schemaVersion, prefix.getPrefix(), perms);
+ }
+
+ @Override
+ public void deletePermissions(VContext ctx, PrefixRange prefix) throws VException {
+ this.client.deletePermissions(ctx, this.schemaVersion, prefix.getPrefix());
+ }
+}
+
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/AccessController.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/AccessController.java
index 2bb241b..35376ad 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/AccessController.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/AccessController.java
@@ -37,7 +37,7 @@
*
* @param ctx Vanadium context
* @return object permissions along with its version number. The returned map
- * is guaranteed to be non-{@code null} and contain exactly one member.
+ * is guaranteed to be non-{@code null} and contain exactly one element
* @throws VException if the object permissions couldn't be retrieved
*/
Map<String, Permissions> getPermissions(VContext ctx) throws VException;
diff --git a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/Util.java b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/Util.java
index 2d5cf07..9bb34f2 100644
--- a/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/Util.java
+++ b/lib/src/main/java/io/v/syncbase/v23/services/syncbase/util/Util.java
@@ -4,10 +4,25 @@
package io.v.syncbase.v23.services.syncbase.util;
+import com.google.common.base.Charsets;
+
+import java.io.EOFException;
import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+
+import io.v.impl.google.naming.NamingUtil;
+import io.v.v23.InputChannel;
+import io.v.v23.V;
+import io.v.v23.context.VContext;
+import io.v.v23.namespace.Namespace;
+import io.v.v23.naming.GlobReply;
+import io.v.v23.verror.VException;
/**
- * Various syncbase utility methods.
+ * Various NoSQL utility methods.
*/
public class Util {
/**
@@ -39,5 +54,66 @@
}
}
+ /**
+ * Performs {@link Namespace#glob Namespace.glob("name/*")} and returns a
+ * sorted list of results.
+ *
+ * @param ctx Vanadium context
+ * @param globName name used for globbing
+ * @return a sorted list of results of
+ * {@link Namespace#glob Namespace.glob("name/*")}
+ * @throws VException if a glob error occurred
+ */
+ public static String[] list(VContext ctx, String globName) throws VException {
+ Namespace n = V.getNamespace(ctx);
+ InputChannel<GlobReply> chan = n.glob(ctx, NamingUtil.join(globName, "*"));
+ ArrayList<String> names = new ArrayList<String>();
+ try {
+ for (GlobReply reply : chan) {
+ if (reply instanceof GlobReply.Entry) {
+ String fullName = ((GlobReply.Entry) reply).getName();
+ // NOTE(nlacasse): The names that come back from Glob are all
+ // rooted. We only want the last part of the name, so we must chop
+ // off everything before the final '/'. Since endpoints can
+ // themselves contain slashes, we have to remove the endpoint from
+ // the name first.
+ String name = NamingUtil.splitAddressName(fullName).get(1);
+ int idx = name.lastIndexOf('/');
+ if (idx != -1) {
+ name = name.substring(idx + 1, name.length());
+ }
+ names.add(name);
+ } else if (reply instanceof GlobReply.Error) {
+ throw ((GlobReply.Error) reply).getElem().getError();
+ } else if (reply == null) {
+ throw new VException("null glob() reply");
+ } else {
+ throw new VException("Unrecognized glob() reply type: " + reply.getClass());
+ }
+ }
+ } catch (RuntimeException e) { // error during iteration
+ throw (VException) e.getCause();
+ }
+ Collections.sort(names, Collator.getInstance());
+ return names.toArray(new String[names.size()]);
+ }
+
+ /**
+ * Returns the UTF-8 encoding of the provided string.
+ */
+ public static byte[] getBytes(String s) {
+ if (s == null) {
+ s = "";
+ }
+ return s.getBytes(Charsets.UTF_8);
+ }
+
+ /**
+ * Returns the UTF-8 decoded string.
+ */
+ public static String getString(byte[] bytes) {
+ return new String(bytes, Charsets.UTF_8);
+ }
+
private Util() {}
}
diff --git a/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilPrefixRangeLimitTest.java b/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilPrefixRangeLimitTest.java
new file mode 100644
index 0000000..fcc4e8d
--- /dev/null
+++ b/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilPrefixRangeLimitTest.java
@@ -0,0 +1,58 @@
+// 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.syncbase.v23.services.syncbase.util;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+/**
+ * Unit tests for {@link Util#prefixRangeStart} and {@link Util#prefixRangeLimit}.
+ */
+@RunWith(Parameterized.class)
+public class UtilPrefixRangeLimitTest {
+ private final String prefix;
+ private final String expectedStart;
+ private final String expectedLimit;
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][]{
+ {"", "", ""},
+ {"a", "a", "b"},
+ {"aa", "aa", "ab"},
+ {"\u00fe", "\u00fe", "\u00ff"},
+ {"a\u00fe", "a\u00fe", "a\u00ff"},
+ {"aa\u00fe", "aa\u00fe", "aa\u00ff"},
+ {"a\u00ff", "a\u00ff", "b"},
+ {"aa\u00ff", "aa\u00ff", "ab"},
+ {"a\u00ff\u00ff", "a\u00ff\u00ff", "b"},
+ {"aa\u00ff\u00ff", "aa\u00ff\u00ff", "ab"},
+ {"\u00ff", "\u00ff", ""},
+ {"\u00ff\u00ff", "\u00ff\u00ff", ""}
+ });
+ }
+
+ public UtilPrefixRangeLimitTest(String prefix, String start, String limit) {
+ this.prefix = prefix;
+ this.expectedStart = start;
+ this.expectedLimit = limit;
+ }
+
+ @Test
+ public void testPrefixRangeStart() {
+ assertThat(Util.prefixRangeStart(this.prefix)).isEqualTo(this.expectedStart);
+ }
+
+ @Test
+ public void testPrefixRangeLimit() {
+ assertThat(Util.prefixRangeLimit(this.prefix)).isEqualTo(this.expectedLimit);
+ }
+}
\ No newline at end of file
diff --git a/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilTest.java b/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilTest.java
deleted file mode 100644
index c5a2aeb..0000000
--- a/lib/src/test/java/io/v/syncbase/v23/services/syncbase/util/UtilTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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.syncbase.v23.services.syncbase.util;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assert_;
-
-import java.util.Arrays;
-import java.util.List;
-
-import junit.framework.TestCase;
-
-/**
- * Tests the various {@link Util} methods.
- */
-public class UtilTest extends TestCase {
- public void testPrefixRange() {
- List<String[]> tests = Arrays.asList(new String[][] {
- {"", "", ""},
- {"a", "a", "b"},
- {"aa", "aa", "ab"},
- {"\u00fe", "\u00fe", "\u00ff"},
- {"a\u00fe", "a\u00fe", "a\u00ff"},
- {"aa\u00fe", "aa\u00fe", "aa\u00ff"},
- {"a\u00ff", "a\u00ff", "b"},
- {"aa\u00ff", "aa\u00ff", "ab"},
- {"a\u00ff\u00ff", "a\u00ff\u00ff", "b"},
- {"aa\u00ff\u00ff", "aa\u00ff\u00ff", "ab"},
- {"\u00ff", "\u00ff", ""},
- {"\u00ff\u00ff", "\u00ff\u00ff", ""}
- });
- for (String[] test : tests) {
- String prefix = test[0];
- String start = test[1];
- String limit = test[2];
- String actualStart = Util.prefixRangeStart(prefix);
- String actualLimit = Util.prefixRangeLimit(prefix);
- assert_().withFailureMessage("Failed for prefix: " + prefix)
- .that(actualStart).isEqualTo(start);
- assert_().withFailureMessage("Failed for prefix: " + prefix)
- .that(actualLimit).isEqualTo(limit);
- }
- }
-}