Skip to content

Commit 50c48aa

Browse files
committed
Improving datastore example to include all CRUD operations and query
1 parent 5d3520c commit 50c48aa

6 files changed

Lines changed: 243 additions & 32 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
<dependency>
8080
<groupId>com.google.apis</groupId>
8181
<artifactId>google-api-services-datastore-protobuf</artifactId>
82-
<version>v1beta2-rev1-2.1.0</version>
82+
<version>v1beta2-rev1-2.1.2</version>
8383
<scope>compile</scope>
8484
</dependency>
8585
<dependency>

src/main/java/com/google/gcloud/datastore/DatastoreHelper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ static Entity add(DatastoreWriter writer, FullEntity<?> entity) {
4747
}
4848

4949
static KeyFactory newKeyFactory(DatastoreServiceOptions options) {
50-
return new KeyFactory(options.dataset()).namespace(options.namespace());
50+
return new KeyFactory(options.dataset(), options.namespace());
5151
}
5252

5353
/**

src/main/java/com/google/gcloud/datastore/DateTime.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
*
3434
* @see <a href="https://cloud.google.com/datastore/docs/concepts/entities">Google Cloud Datastore Entities, Properties, and Keys</a>
3535
*/
36-
public final class DateTime extends Serializable<DatastoreV1.Value> {
36+
public final class DateTime extends Serializable<DatastoreV1.Value>
37+
implements Comparable<DateTime> {
3738

3839
private static final long serialVersionUID = 7343324797621228378L;
3940

@@ -53,6 +54,11 @@ public int hashCode() {
5354
return (int) timestampMicroseconds;
5455
}
5556

57+
@Override
58+
public int compareTo(DateTime other) {
59+
return toDate().compareTo(other.toDate());
60+
}
61+
5662
@Override
5763
public boolean equals(Object obj) {
5864
return obj == this || obj instanceof DateTime

src/main/java/com/google/gcloud/datastore/KeyFactory.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,18 @@
2424
*/
2525
public final class KeyFactory extends BaseKey.Builder<KeyFactory> {
2626

27+
private final String ds;
28+
private final String ns;
29+
2730
public KeyFactory(String dataset) {
31+
this(dataset, null);
32+
}
33+
34+
public KeyFactory(String dataset, String namespace) {
2835
super(dataset);
36+
namespace(namespace);
37+
this.ds = dataset;
38+
this.ns = namespace;
2939
}
3040

3141
public IncompleteKey newKey() {
@@ -46,6 +56,18 @@ public Key newKey(long id) {
4656
return new Key(dataset, namespace, path);
4757
}
4858

59+
/**
60+
* Resets the KeyFactory to its initial state.
61+
* @return {@code this} for chaining.
62+
*/
63+
public KeyFactory reset() {
64+
dataset(ds);
65+
namespace(ns);
66+
kind = null;
67+
ancestors.clear();
68+
return this;
69+
}
70+
4971
@Override
5072
protected IncompleteKey build() {
5173
return newKey();

src/main/java/com/google/gcloud/examples/DatastoreExample.java

Lines changed: 174 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,56 +19,201 @@
1919
import com.google.gcloud.datastore.DatastoreService;
2020
import com.google.gcloud.datastore.DatastoreServiceFactory;
2121
import com.google.gcloud.datastore.DatastoreServiceOptions;
22+
import com.google.gcloud.datastore.DateTime;
2223
import com.google.gcloud.datastore.Entity;
24+
import com.google.gcloud.datastore.FullEntity;
25+
import com.google.gcloud.datastore.IncompleteKey;
2326
import com.google.gcloud.datastore.Key;
2427
import com.google.gcloud.datastore.KeyFactory;
28+
import com.google.gcloud.datastore.Query;
29+
import com.google.gcloud.datastore.Query.ResultType;
30+
import com.google.gcloud.datastore.QueryResults;
31+
import com.google.gcloud.datastore.StructuredQuery.PropertyFilter;
32+
import com.google.gcloud.datastore.Transaction;
33+
34+
import java.util.Arrays;
35+
import java.util.HashMap;
36+
import java.util.Map;
37+
import java.util.TreeMap;
2538

2639
/**
2740
* An example of using the Google Cloud Datastore.
2841
* <p>
42+
* This example adds, display or clear comments for a given user.
43+
* <p>
2944
* Steps needed for running the example:<ol>
3045
* <li>login using gcloud SDK - {@code gcloud auth login}.</li>
3146
* <li>compile using maven - {@code mvn compile}</li>
3247
* <li>run using maven - {@code mvn exec:java
33-
* -Dexec.mainClass="com.google.gcloud.examples.DatastoreExample" -Dexec.arguments="dataset"}</li>
48+
* -Dexec.mainClass="com.google.gcloud.examples.DatastoreExample"
49+
* -Dexec.args="dataset [user] [delete|display|add comment]"}</li>
3450
* </ol>
3551
*/
3652
public class DatastoreExample {
3753

38-
public static void main(String[] args) {
39-
if (args.length != 1) {
40-
System.err.println("DatastoreExample requires one argument for dataset");
41-
return;
54+
private final static String USER_KIND = "_DS_EXAMPLE_USER";
55+
private final static String COMMENT_KIND = "_DS_EXAMPLE_COMMENT";
56+
private final static String NAMESPACE = "gcloud_java_example";
57+
private final static String DEFAULT_ACTION = "display";
58+
private final static Map<String, DatastoreAction> ACTIONS = new HashMap<>();
59+
60+
private interface DatastoreAction {
61+
void run(Transaction tx, Key userKey, String... args);
62+
String getRequiredParams();
63+
}
64+
65+
private static class DeleteAction implements DatastoreAction {
66+
@Override
67+
public void run(Transaction tx, Key userKey, String... args) {
68+
Entity user = tx.get(userKey);
69+
if (user == null) {
70+
System.out.println("Nothing to delete, user does not exists.");
71+
return;
72+
}
73+
Query<Key> query = Query.keyQueryBuilder()
74+
.namespace(NAMESPACE)
75+
.kind(COMMENT_KIND)
76+
.filter(PropertyFilter.hasAncestor(userKey))
77+
.build();
78+
QueryResults<Key> comments = tx.run(query);
79+
int count = 0;
80+
while (comments.hasNext()) {
81+
tx.delete(comments.next());
82+
count++;
83+
}
84+
tx.delete(userKey);
85+
System.out.printf("Deleting user '%s' and %d comments.\n", userKey.name(), count);
86+
}
87+
88+
@Override
89+
public String getRequiredParams() {
90+
return "";
91+
}
92+
}
93+
94+
private static class DisplayAction implements DatastoreAction {
95+
@Override
96+
public void run(Transaction tx, Key userKey, String... args) {
97+
Entity user = tx.get(userKey);
98+
if (user == null) {
99+
System.out.println("No comments for '" + userKey.name() + "'.");
100+
return;
101+
}
102+
System.out.println("User: " + userKey.name());
103+
// ORDER BY timestamp";
104+
String gql = "SELECT * FROM " + COMMENT_KIND + " WHERE __key__ HAS ANCESTOR @1";
105+
Query<Entity> query = Query.gqlQueryBuilder(ResultType.ENTITY, gql)
106+
.namespace(NAMESPACE)
107+
.addBinding(userKey)
108+
.build();
109+
QueryResults<Entity> results = tx.run(query);
110+
// We could have added "ORDER BY timestamp" to the query to avoid the sorting bellow
111+
// but that would require adding an ancestor index for timestamp
112+
// see: https://cloud.google.com/datastore/docs/tools/indexconfig
113+
Map<DateTime, String> sortedComments = new TreeMap<>();
114+
while (results.hasNext()) {
115+
Entity result = results.next();
116+
sortedComments.put(result.getDateTime("timestamp"), result.getString("content"));
117+
}
118+
for (Map.Entry<DateTime, String> entry : sortedComments.entrySet()) {
119+
System.out.printf("\t%s: %s\n", entry.getKey(), entry.getValue());
120+
}
42121
}
43-
String dataset = args[0];
44-
// If you want to access a local Datastore running via the gcd sdk, do
45-
// DatastoreServiceOptions options = DatastoreServiceOptions.builder()
46-
// .dataset(DATASET)
47-
// .host("http://localhost:" + LocalGcdHelper.PORT)
48-
// .build();
49-
DatastoreServiceOptions options = DatastoreServiceOptions.builder().dataset(dataset).build();
50-
DatastoreService datastore = DatastoreServiceFactory.getDefault(options);
51-
KeyFactory keyFactory = datastore.newKeyFactory().kind("Person");
52-
Key key = keyFactory.newKey("Jimmy");
53122

54-
System.out.println("Trying to get the entity by its key!");
55-
Entity entity = datastore.get(key);
123+
@Override
124+
public String getRequiredParams() {
125+
return "";
126+
}
127+
}
56128

57-
if (entity == null) {
58-
System.out.println("Entity not found! Creating it!");
59-
entity = Entity.builder(key)
60-
.set("age", 30L)
129+
private static class AddAction implements DatastoreAction {
130+
@Override
131+
public void run(Transaction tx, Key userKey, String... args) {
132+
Entity user = tx.get(userKey);
133+
if (user == null) {
134+
System.out.println("Adding a new user.");
135+
user = Entity.builder(userKey)
136+
.set("count", 1L)
137+
.build();
138+
tx.add(user);
139+
}
140+
String content = "No comment.";
141+
if (args.length > 0) {
142+
StringBuilder stBuilder = new StringBuilder();
143+
for (String arg : args) {
144+
stBuilder.append(arg).append(' ');
145+
}
146+
stBuilder.setLength(stBuilder.length() - 1);
147+
content = stBuilder.toString();
148+
}
149+
IncompleteKey commentKey = IncompleteKey.builder(userKey, COMMENT_KIND).build();
150+
FullEntity<IncompleteKey> comment = FullEntity.builder(commentKey)
151+
.set("content", content)
152+
.set("timestamp", DateTime.now())
61153
.build();
62-
datastore.add(entity);
154+
tx.addWithDeferredIdAllocation(comment);
155+
System.out.println("Adding a comment to user '" + userKey.name() + "'.");
63156
}
64157

65-
System.out.println("Going to modify entity: " + entity);
66-
Entity.Builder builder = Entity.builder(entity);
67-
builder.set("f", 30);
68-
datastore.put(entity);
158+
@Override
159+
public String getRequiredParams() {
160+
return "comment";
161+
}
162+
}
163+
164+
static {
165+
ACTIONS.put("delete", new DeleteAction());
166+
ACTIONS.put("add", new AddAction());
167+
ACTIONS.put("display", new DisplayAction());
168+
}
69169

70-
System.out.println("Trying again to get the entity by its key!");
71-
key = Key.builder(dataset, "Person", "Jimmy").build();
72-
System.out.println("Got entity: " + entity);
170+
public static void main(String... args) {
171+
DatastoreAction action = null;
172+
DatastoreService datastore = null;
173+
Key key = null;
174+
if (args.length > 0) {
175+
String dataset = args[0];
176+
// If you want to access a local Datastore running via the gcd sdk, do
177+
// DatastoreServiceOptions options = DatastoreServiceOptions.builder()
178+
// .dataset(dataset)
179+
// .namespace(NAMESPACE)
180+
// .host("http://localhost:8080")
181+
// .build();
182+
DatastoreServiceOptions options = DatastoreServiceOptions.builder()
183+
.dataset(dataset)
184+
.namespace(NAMESPACE)
185+
.build();
186+
String name = args.length > 1 ? args[1] : System.getProperty("user.name");
187+
datastore = DatastoreServiceFactory.getDefault(options);
188+
KeyFactory keyFactory = datastore.newKeyFactory().kind(USER_KIND);
189+
key = keyFactory.newKey(name);
190+
String actionName = args.length > 2 ? args[2].toLowerCase() : DEFAULT_ACTION;
191+
action = ACTIONS.get(actionName);
192+
}
193+
if (action == null) {
194+
StringBuilder actionAndParams = new StringBuilder();
195+
for (Map.Entry<String, DatastoreAction> entry : ACTIONS.entrySet()) {
196+
actionAndParams.append(entry.getKey());
197+
String param = entry.getValue().getRequiredParams();
198+
if (param != null && !param.isEmpty()) {
199+
actionAndParams.append(' ').append(param);
200+
}
201+
actionAndParams.append('|');
202+
}
203+
actionAndParams.setLength(actionAndParams.length() - 1);
204+
System.out.printf("Usage: %s dataset [user] [%s]\n",
205+
DatastoreExample.class.getSimpleName(), actionAndParams);
206+
return;
207+
}
208+
args = args.length > 3 ? Arrays.copyOfRange(args, 3, args.length): new String []{};
209+
Transaction tx = datastore.newTransaction();
210+
try {
211+
action.run(tx, key, args);
212+
tx.commit();
213+
} finally {
214+
if (tx.active()) {
215+
tx.rollback();
216+
}
217+
}
73218
}
74219
}

src/test/java/com/google/gcloud/datastore/KeyFactoryTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.google.gcloud.datastore;
1818

1919
import static junit.framework.TestCase.assertEquals;
20+
import static junit.framework.TestCase.assertNull;
21+
import static org.junit.Assert.assertTrue;
2022

2123
import org.junit.Before;
2224
import org.junit.Test;
@@ -34,6 +36,42 @@ public void setUp() {
3436
keyFactory = new KeyFactory(DATASET).kind("k");
3537
}
3638

39+
@Test
40+
public void testReset() {
41+
IncompleteKey key =
42+
keyFactory.dataset("ds1").namespace("ns1").ancestors(PathElement.of("p", 1)).build();
43+
assertEquals("k", key.kind());
44+
assertEquals("ds1", key.dataset());
45+
assertEquals("ns1", key.namespace());
46+
assertEquals(1, key.ancestors().size());
47+
48+
keyFactory.reset();
49+
try {
50+
keyFactory.newKey(1);
51+
} catch (NullPointerException ex) {
52+
assertEquals("kind must not be null", ex.getMessage());
53+
}
54+
keyFactory.kind("k1");
55+
key = keyFactory.newKey();
56+
assertEquals("k1", key.kind());
57+
assertEquals(DATASET, key.dataset());
58+
assertNull(key.namespace());
59+
assertTrue(key.ancestors().isEmpty());
60+
61+
keyFactory = new KeyFactory(DATASET, "ns1").kind("k");
62+
key = keyFactory.newKey();
63+
assertEquals(DATASET, key.dataset());
64+
assertEquals("ns1", key.namespace());
65+
key = keyFactory.dataset("bla1").namespace("bla2").build();
66+
assertEquals("bla1", key.dataset());
67+
assertEquals("bla2", key.namespace());
68+
keyFactory.reset().kind("kind");
69+
key = keyFactory.newKey();
70+
assertEquals(DATASET, key.dataset());
71+
assertEquals("ns1", key.namespace());
72+
assertEquals("kind", key.kind());
73+
}
74+
3775
@Test
3876
public void testNewKey() throws Exception {
3977
Key key = keyFactory.newKey(1);

0 commit comments

Comments
 (0)