diff --git a/appengine/guestbook-cloud-datastore/README.md b/appengine/guestbook-cloud-datastore/README.md
new file mode 100644
index 00000000000..1c6d6957c8a
--- /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:run
+
+## Deploying
+
+Modify `appengine-web.xml` to reflect your app ID and version, then:
+
+ mvn clean appengine:deploy
diff --git a/appengine/guestbook-cloud-datastore/pom.xml b/appengine/guestbook-cloud-datastore/pom.xml
new file mode 100644
index 00000000000..bc7683efe06
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/pom.xml
@@ -0,0 +1,101 @@
+
+
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 com.google.common.base.MoreObjects;
+
+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;
+ }
+
+ 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 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.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
+
+import java.util.List;
+import java.util.Objects;
+
+//[START all]
+public class Guestbook {
+ private static final KeyFactory keyFactory = getKeyFactory(Guestbook.class);
+ private final Key key;
+
+ public final String book;
+
+ public Guestbook(String book) {
+ this.book = book == null ? "default" : book;
+ key =
+ keyFactory.newKey(
+ this.book); // There is a 1:1 mapping between Guestbook names and Guestbook objects
+ }
+
+ public Key getKey() {
+ return key;
+ }
+
+ public List 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 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..bdcb11ebd86
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,8 @@
+
+ Hello, ${fn:escapeXml(user.nickname)}! (You can
+ sign out.) Hello!
+ Sign in
+ to include your name with greetings you post. Guestbook '${fn:escapeXml(guestbookName)}' has no messages. Messages in Guestbook '${fn:escapeXml(guestbookName)}'. ${fn:escapeXml(greeting_user)} wrote: 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class GreetingTest {
+
+ @Before
+ public void setUp() {
+ TestUtils.startDatastore();
+ }
+
+ @Test
+ public void testSaveGreeting() throws Exception {
+ Greeting greeting = new Greeting(null, "Test!");
+ greeting.save();
+
+ Guestbook guestbook = new Guestbook(null);
+ List 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 org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@RunWith(JUnit4.class)
+public class SignGuestbookServletTest {
+ private final LocalServiceTestHelper helper = new LocalServiceTestHelper();
+
+ @Mock private HttpServletRequest mockRequest;
+ @Mock private HttpServletResponse mockResponse;
+
+ private SignGuestbookServlet signGuestbookServlet;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ // Sets up the UserServiceFactory used in SignGuestbookServlet (but not in this test)
+ helper.setUp();
+
+ signGuestbookServlet = new SignGuestbookServlet();
+ TestUtils.startDatastore();
+ }
+
+ @Test
+ public void doPost_userNotLoggedIn() throws Exception {
+ String testBook = "default";
+ when(mockRequest.getParameter("guestbookName")).thenReturn(testBook);
+ String testGreeting = "beep!";
+ when(mockRequest.getParameter("content")).thenReturn(testGreeting);
+
+ signGuestbookServlet.doPost(mockRequest, mockResponse);
+ Guestbook guestbook = new Guestbook(testBook);
+ List${fn:escapeXml(greeting_content)}
+<%
+ }
+ }
+%>
+
+
+<%-- //[END datastore]--%>
+
+
+
+
+<%-- //[END all]--%>
diff --git a/appengine/guestbook-cloud-datastore/src/main/webapp/stylesheets/main.css b/appengine/guestbook-cloud-datastore/src/main/webapp/stylesheets/main.css
new file mode 100644
index 00000000000..05d72d5536d
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/main/webapp/stylesheets/main.css
@@ -0,0 +1,4 @@
+body {
+ font-family: Verdana, Helvetica, sans-serif;
+ background-color: #FFFFCC;
+}
diff --git a/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/GreetingTest.java b/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/GreetingTest.java
new file mode 100644
index 00000000000..6ddaa732d37
--- /dev/null
+++ b/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/GreetingTest.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ *