diff --git a/appengine/guestbook-cloud-datastore/README.md b/appengine/guestbook-cloud-datastore/README.md
new file mode 100644
index 00000000000..94b3edf3de9
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/README.md
@@ -0,0 +1,28 @@
+# appengine/guestbook-cloud-datastore
+
+An App Engine guestbook using Java, Maven, and the Cloud Datastore API via
+[google-cloud-java](https://github.com/GoogleCloudPlatform/google-cloud-java).
+
+Please ask questions on [StackOverflow](http://stackoverflow.com/questions/tagged/google-app-engine).
+
+## Running Locally
+
+First, pick a project ID. You can create a project in the [Cloud Console] if you'd like, though this
+isn't necessary unless you'd like to deploy the sample.
+
+Second, modify `Persistence.java`: replace `your-project-id-here` with the project ID you picked.
+
+Then start the [Cloud Datastore Emulator](https://cloud.google.com/datastore/docs/tools/datastore-emulator):
+
+ gcloud beta emulators datastore start --project=YOUR_PROJECT_ID_HERE
+
+Finally, in a new shell, [set the Datastore Emulator environmental variables](https://cloud.google.com/datastore/docs/tools/datastore-emulator#setting_environment_variables)
+and run
+
+ mvn clean appengine:devserver
+
+## Deploying
+
+Modify `appengine-web.xml` to reflect your app ID and version, then:
+
+ mvn clean appengine:update
diff --git a/appengine/guestbook-cloud-datastore/pom.xml b/appengine/guestbook-cloud-datastore/pom.xml
new file mode 100644
index 00000000000..d40dafdede5
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/pom.xml
@@ -0,0 +1,112 @@
+
+
+
+ 4.0.0
+ war
+ 1.0-SNAPSHOT
+
+ com.example.appengine
+ appengine-guestbook-cloud-datastore
+
+ 19.0
+
+
+ com.google.cloud
+ doc-samples
+ 1.0.0
+ ../..
+
+
+
+
+ 3.3.9
+
+
+
+
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ ${appengine.sdk.version}
+
+
+ javax.servlet
+ servlet-api
+ 2.5
+ provided
+
+
+ jstl
+ jstl
+ 1.2
+
+
+
+ com.google.cloud
+ google-cloud
+ 0.4.0
+
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ org.mockito
+ mockito-all
+ 1.10.19
+ test
+
+
+ com.google.appengine
+ appengine-testing
+ ${appengine.sdk.version}
+ test
+
+
+ com.google.appengine
+ appengine-api-stubs
+ ${appengine.sdk.version}
+ test
+
+
+ com.google.appengine
+ appengine-tools-sdk
+ ${appengine.sdk.version}
+ test
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
+
+
+ com.google.appengine
+ appengine-maven-plugin
+ ${appengine.sdk.version}
+
+ false
+
+
+
+
+
+
+
+
+
diff --git a/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Greeting.java b/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Greeting.java
new file mode 100644
index 00000000000..988118174c0
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Greeting.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//[START all]
+package com.example.guestbook;
+
+import static com.example.guestbook.Persistence.getDatastore;
+
+import com.google.cloud.datastore.DateTime;
+import com.google.cloud.datastore.Entity;
+import com.google.cloud.datastore.FullEntity;
+import com.google.cloud.datastore.FullEntity.Builder;
+import com.google.cloud.datastore.IncompleteKey;
+import com.google.cloud.datastore.Key;
+import java.util.Date;
+import java.util.Objects;
+
+public class Greeting {
+ private Guestbook book;
+
+ public Key key;
+
+ public String authorEmail;
+ public String authorId;
+ public String content;
+ public Date date;
+
+ public Greeting() {
+ date = new Date();
+ }
+
+ public Greeting(String book, String content) {
+ this();
+ this.book = new Guestbook(book);
+ this.content = content;
+ }
+
+ public Greeting(String book, String content, String id, String email) {
+ this(book, content);
+ authorEmail = email;
+ authorId = id;
+ }
+
+ /**
+ * Load greeting from Datastore entity
+ *
+ * @param entity
+ */
+ public Greeting(Entity entity) {
+ key = entity.hasKey() ? entity.key() : null;
+ authorEmail = entity.contains("authorEmail") ? entity.getString("authorEmail") : null;
+ authorId = entity.contains("authorId") ? entity.getString("authorId") : null;
+ date = entity.contains("date") ? entity.getDateTime("date").toDate() : null;
+ content = entity.contains("content") ? entity.getString("content") : null;
+ }
+
+ public void save() {
+ if (key == null) {
+ key = getDatastore().allocateId(makeIncompleteKey()); // Give this greeting a unique ID
+ }
+
+ Builder builder = FullEntity.builder(key);
+
+ if (authorEmail != null) {
+ builder.set("authorEmail", authorEmail);
+ }
+
+ if (authorId != null) {
+ builder.set("authorId", authorId);
+ }
+
+ builder.set("content", content);
+ builder.set("date", DateTime.copyFrom(date));
+
+ getDatastore().put(builder.build());
+ }
+
+ private IncompleteKey makeIncompleteKey() {
+ // The book is our ancestor key.
+ return Key.builder(book.getKey(), "Greeting").build();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Greeting greeting = (Greeting) o;
+ return Objects.equals(key, greeting.key) &&
+ Objects.equals(authorEmail, greeting.authorEmail) &&
+ Objects.equals(authorId, greeting.authorId) &&
+ Objects.equals(content, greeting.content) &&
+ Objects.equals(date, greeting.date);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(key, authorEmail, authorId, content, date);
+ }
+}
+//[END all]
\ No newline at end of file
diff --git a/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Guestbook.java b/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Guestbook.java
new file mode 100644
index 00000000000..694010ab5c5
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Guestbook.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.guestbook;
+
+import static com.example.guestbook.Persistence.getDatastore;
+import static com.example.guestbook.Persistence.getKeyFactory;
+import static com.google.cloud.datastore.StructuredQuery.OrderBy.desc;
+import static com.google.cloud.datastore.StructuredQuery.PropertyFilter.hasAncestor;
+
+import com.google.cloud.datastore.Entity;
+import com.google.cloud.datastore.EntityQuery;
+import com.google.cloud.datastore.Key;
+import com.google.cloud.datastore.KeyFactory;
+import com.google.cloud.datastore.Query;
+import com.google.cloud.datastore.QueryResults;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+import java.util.List;
+
+//[START all]
+public class Guestbook {
+ private static final KeyFactory kf = getKeyFactory(Guestbook.class);
+
+ private final Key key;
+ public final String book;
+
+ public Guestbook(String book) {
+ this.book = book == null ? "default" : book;
+ key = kf.newKey(this.book); // There is a 1:1 mapping between Guestbook names and Guestbook objects
+ }
+
+ public Key getKey() {
+ return key;
+ }
+
+ public List getGreetings() {
+ // This query requires the index defined in index.yaml to work because of the orderBy on date.
+ EntityQuery query = Query.entityQueryBuilder()
+ .kind("Greeting")
+ .filter(hasAncestor(key))
+ .orderBy(desc("date"))
+ .limit(5)
+ .build();
+
+ QueryResults results = getDatastore().run(query);
+
+ Builder resultListBuilder = ImmutableList.builder();
+ while (results.hasNext()) {
+ resultListBuilder.add(new Greeting(results.next()));
+ }
+
+ return resultListBuilder.build();
+ }
+}
+//[END all]
\ No newline at end of file
diff --git a/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Persistence.java b/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Persistence.java
new file mode 100644
index 00000000000..3aee65c89cc
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Persistence.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.guestbook;
+
+import com.google.cloud.datastore.Datastore;
+import com.google.cloud.datastore.DatastoreOptions;
+import com.google.cloud.datastore.KeyFactory;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+//[START all]
+public class Persistence {
+ private static AtomicReference datastore = new AtomicReference<>();
+
+ public static Datastore getDatastore() {
+ if (datastore.get() == null) {
+ datastore.set(DatastoreOptions.builder().projectId("your-project-id-here").build().service());
+ }
+
+ return datastore.get();
+ }
+
+ public static KeyFactory getKeyFactory(Class> c) {
+ return getDatastore().newKeyFactory().kind(c.getSimpleName());
+ }
+
+ public static void setDatastore(Datastore datastore) {
+ Persistence.datastore.set(datastore);
+ }
+}
+//[END all]
diff --git a/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/SignGuestbookServlet.java b/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/SignGuestbookServlet.java
new file mode 100644
index 00000000000..eec1a34c2a8
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/SignGuestbookServlet.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//[START all]
+package com.example.guestbook;
+
+import com.google.appengine.api.users.User;
+import com.google.appengine.api.users.UserService;
+import com.google.appengine.api.users.UserServiceFactory;
+import java.io.IOException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+//[START all]
+public class SignGuestbookServlet extends HttpServlet {
+ // Process the HTTP POST of the form
+ @Override
+ public void doPost(HttpServletRequest req, HttpServletResponse resp)
+ throws IOException {
+ Greeting greeting;
+
+ UserService userService = UserServiceFactory.getUserService();
+ User user = userService.getCurrentUser(); // Find out who the user is.
+
+ String guestbookName = req.getParameter("guestbookName");
+ String content = req.getParameter("content");
+ if (user != null) {
+ greeting = new Greeting(guestbookName, content, user.getUserId(), user.getEmail());
+ } else {
+ greeting = new Greeting(guestbookName, content);
+ }
+
+ greeting.save();
+
+ resp.sendRedirect("/guestbook.jsp?guestbookName=" + guestbookName);
+ }
+}
+//[END all]
diff --git a/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..14ea1140aa7
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,10 @@
+
+
+ your-app-id-here
+ your-app-version-here
+ true
+
+
+
+
+
diff --git a/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/index.yaml b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/index.yaml
new file mode 100644
index 00000000000..03f9df1f728
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/index.yaml
@@ -0,0 +1,7 @@
+indexes:
+
+- kind: Greeting
+ ancestor: yes
+ properties:
+ - name: date
+ direction: desc
\ No newline at end of file
diff --git a/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/logging.properties b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/logging.properties
new file mode 100644
index 00000000000..a17206681f0
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/logging.properties
@@ -0,0 +1,13 @@
+# A default java.util.logging configuration.
+# (All App Engine logging is through java.util.logging by default).
+#
+# To use this configuration, copy it into your application's WEB-INF
+# folder and add the following to your appengine-web.xml:
+#
+#
+#
+#
+#
+
+# Set the default logging level for all loggers to WARNING
+.level = WARNING
diff --git a/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/web.xml b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..1ffb6bb5853
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ sign
+ com.example.guestbook.SignGuestbookServlet
+ 1
+
+
+
+ sign
+ /sign
+
+
+
+ guestbook.jsp
+
+
+
diff --git a/appengine/guestbook-cloud-datastore/src/main/webapp/guestbook.jsp b/appengine/guestbook-cloud-datastore/src/main/webapp/guestbook.jsp
new file mode 100644
index 00000000000..163fd1a29b3
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/main/webapp/guestbook.jsp
@@ -0,0 +1,98 @@
+<%-- //[START all]--%>
+<%@ page contentType="text/html;charset=UTF-8" language="java" %>
+<%@ page import="com.google.appengine.api.users.User" %>
+<%@ page import="com.google.appengine.api.users.UserService" %>
+<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>
+
+<%-- //[START imports]--%>
+<%@ page import="com.example.guestbook.Greeting" %>
+<%@ page import="com.example.guestbook.Guestbook" %>
+<%-- //[END imports]--%>
+
+<%@ page import="java.util.List" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+
+
+
+
+
+
+
+
+<%
+ String guestbookName = request.getParameter("guestbookName");
+ if (guestbookName == null) {
+ guestbookName = "default";
+ }
+ pageContext.setAttribute("guestbookName", guestbookName);
+ UserService userService = UserServiceFactory.getUserService();
+ User user = userService.getCurrentUser();
+ if (user != null) {
+ pageContext.setAttribute("user", user);
+%>
+
+
Hello, ${fn:escapeXml(user.nickname)}! (You can
+ sign out.)
+<%
+ } else {
+%>
+
Hello!
+ Sign in
+ to include your name with greetings you post.
+<%
+ }
+%>
+
+<%-- //[START datastore]--%>
+<%
+ // Create the correct Ancestor key
+ Guestbook theBook = new Guestbook(guestbookName);
+
+ // Run an ancestor query to ensure we see the most up-to-date
+ // view of the Greetings belonging to the selected Guestbook.
+ List greetings = theBook.getGreetings();
+
+ if (greetings.isEmpty()) {
+%>
+
Guestbook '${fn:escapeXml(guestbookName)}' has no messages.
+<%
+ } else {
+%>
+
Messages in Guestbook '${fn:escapeXml(guestbookName)}'.