From e9e32ce9f46dcbd1532ac207e820836c610dfe49 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Thu, 28 Apr 2016 18:25:46 -0700 Subject: [PATCH] Add Datastore indexes samples. Samples are moved from: https://cloud.google.com/appengine/docs/java/datastore/indexes Note: I add a new script `test-devserver.sh` to the testing config. This script runs the `mvn appengine:devserver` plugin, waits for it to start, then verifies that it gets a non-error response from the `/` path. I use this to verify that the `datastore-indexes.xml` files are correct (by disabling autoGenerate, the local devserver throws an error when a query is used without the correct index defined, just as production does). We should probably add any "hello world" or other projects to this check, as well. Anywhere we say to run `mvn appengine:devserver`, it would be good to test that it is correct. --- .travis.yml | 4 + appengine/datastore/indexes-exploding/pom.xml | 91 +++++++++++++++ .../com/example/appengine/IndexesServlet.java | 62 +++++++++++ .../src/main/webapp/WEB-INF/appengine-web.xml | 21 ++++ .../main/webapp/WEB-INF/datastore-indexes.xml | 28 +++++ .../src/main/webapp/WEB-INF/web.xml | 39 +++++++ .../example/appengine/IndexesServletTest.java | 104 ++++++++++++++++++ appengine/datastore/indexes-perfect/pom.xml | 91 +++++++++++++++ .../com/example/appengine/IndexesServlet.java | 98 +++++++++++++++++ .../src/main/webapp/WEB-INF/appengine-web.xml | 21 ++++ .../main/webapp/WEB-INF/datastore-indexes.xml | 27 +++++ .../src/main/webapp/WEB-INF/web.xml | 39 +++++++ .../example/appengine/IndexesServletTest.java | 83 ++++++++++++++ appengine/datastore/indexes/pom.xml | 91 +++++++++++++++ .../com/example/appengine/IndexesServlet.java | 64 +++++++++++ .../src/main/webapp/WEB-INF/appengine-web.xml | 21 ++++ .../main/webapp/WEB-INF/datastore-indexes.xml | 27 +++++ .../indexes/src/main/webapp/WEB-INF/web.xml | 39 +++++++ .../example/appengine/IndexesServletTest.java | 77 +++++++++++++ .../com/example/appengine/IndexesTest.java | 103 +++++++++++++++++ java-repo-tools/test-devserver.sh | 52 +++++++++ pom.xml | 3 + travis.sh | 10 ++ 23 files changed, 1195 insertions(+) create mode 100644 appengine/datastore/indexes-exploding/pom.xml create mode 100644 appengine/datastore/indexes-exploding/src/main/java/com/example/appengine/IndexesServlet.java create mode 100644 appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/appengine-web.xml create mode 100644 appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/datastore-indexes.xml create mode 100644 appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/web.xml create mode 100644 appengine/datastore/indexes-exploding/src/test/java/com/example/appengine/IndexesServletTest.java create mode 100644 appengine/datastore/indexes-perfect/pom.xml create mode 100644 appengine/datastore/indexes-perfect/src/main/java/com/example/appengine/IndexesServlet.java create mode 100644 appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/appengine-web.xml create mode 100644 appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/datastore-indexes.xml create mode 100644 appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/web.xml create mode 100644 appengine/datastore/indexes-perfect/src/test/java/com/example/appengine/IndexesServletTest.java create mode 100644 appengine/datastore/indexes/pom.xml create mode 100644 appengine/datastore/indexes/src/main/java/com/example/appengine/IndexesServlet.java create mode 100644 appengine/datastore/indexes/src/main/webapp/WEB-INF/appengine-web.xml create mode 100644 appengine/datastore/indexes/src/main/webapp/WEB-INF/datastore-indexes.xml create mode 100644 appengine/datastore/indexes/src/main/webapp/WEB-INF/web.xml create mode 100644 appengine/datastore/indexes/src/test/java/com/example/appengine/IndexesServletTest.java create mode 100644 appengine/datastore/src/test/java/com/example/appengine/IndexesTest.java create mode 100755 java-repo-tools/test-devserver.sh diff --git a/.travis.yml b/.travis.yml index 1e703899bbe..69fb0cd9adb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,10 @@ language: java jdk: - oraclejdk8 +addons: + apt: + packages: + - expect before_install: - openssl aes-256-cbc -K $encrypted_37a4f399de75_key -iv $encrypted_37a4f399de75_iv -in service-account.json.enc -out service-account.json -d && export GOOGLE_APPLICATION_CREDENTIALS=$TRAVIS_BUILD_DIR/service-account.json GCLOUD_PROJECT=cloud-samples-tests diff --git a/appengine/datastore/indexes-exploding/pom.xml b/appengine/datastore/indexes-exploding/pom.xml new file mode 100644 index 00000000000..37a2a05154b --- /dev/null +++ b/appengine/datastore/indexes-exploding/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-datastore-indexes-exploding + + com.google.cloud + doc-samples + 1.0.0 + ../../.. + + + + com.google.appengine + appengine-api-1.0-sdk + ${appengine.sdk.version} + + + javax.servlet + servlet-api + jar + provided + + + + + junit + junit + 4.10 + 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 + + + com.google.truth + truth + 0.28 + test + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + + com.google.appengine + appengine-maven-plugin + ${appengine.sdk.version} + + + + diff --git a/appengine/datastore/indexes-exploding/src/main/java/com/example/appengine/IndexesServlet.java b/appengine/datastore/indexes-exploding/src/main/java/com/example/appengine/IndexesServlet.java new file mode 100644 index 00000000000..dff4396b797 --- /dev/null +++ b/appengine/datastore/indexes-exploding/src/main/java/com/example/appengine/IndexesServlet.java @@ -0,0 +1,62 @@ +/* + * 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.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * A servlet to demonstrate the use of Cloud Datastore indexes. + */ +public class IndexesServlet extends HttpServlet { + private final DatastoreService datastore; + + public IndexesServlet() { + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + Query q = + new Query("Widget") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("x", FilterOperator.EQUAL, 1), + new FilterPredicate("y", FilterOperator.EQUAL, "red"))) + .addSort("date", Query.SortDirection.ASCENDING); + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + + PrintWriter out = resp.getWriter(); + out.printf("Got %d widgets.\n", results.size()); + } +} diff --git a/appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..e9d8b21cb8f --- /dev/null +++ b/appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,21 @@ + + + + YOUR-PROJECT-ID + YOUR-VERSION-ID + true + diff --git a/appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/datastore-indexes.xml b/appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/datastore-indexes.xml new file mode 100644 index 00000000000..e0f85d3812d --- /dev/null +++ b/appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/datastore-indexes.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/web.xml b/appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..049fd7a05e7 --- /dev/null +++ b/appengine/datastore/indexes-exploding/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,39 @@ + + + + + indexes-servlet + com.example.appengine.IndexesServlet + + + indexes-servlet + / + + + + + profile + /* + + + CONFIDENTIAL + + + diff --git a/appengine/datastore/indexes-exploding/src/test/java/com/example/appengine/IndexesServletTest.java b/appengine/datastore/indexes-exploding/src/test/java/com/example/appengine/IndexesServletTest.java new file mode 100644 index 00000000000..451f2189d34 --- /dev/null +++ b/appengine/datastore/indexes-exploding/src/test/java/com/example/appengine/IndexesServletTest.java @@ -0,0 +1,104 @@ +/* + * 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.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +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.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Date; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Unit tests for {@link IndexesServlet}. + */ +@RunWith(JUnit4.class) +public class IndexesServletTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting#Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + @Mock private HttpServletRequest mockRequest; + @Mock private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private IndexesServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + helper.setUp(); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new IndexesServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void doGet_emptyDatastore_writesNoWidgets() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + assertThat(responseWriter.toString()) + .named("IndexesServlet response") + .isEqualTo("Got 0 widgets.\n"); + } + + @Test + public void doGet_repeatedPropertyEntities_writesWidgets() throws Exception { + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + // [START exploding_index_example_3] + Entity widget = new Entity("Widget"); + widget.setProperty("x", Arrays.asList(1, 2, 3, 4)); + widget.setProperty("y", Arrays.asList("red", "green", "blue")); + widget.setProperty("date", new Date()); + datastore.put(widget); + // [END exploding_index_example_3] + + servletUnderTest.doGet(mockRequest, mockResponse); + + assertThat(responseWriter.toString()) + .named("IndexesServlet response") + .isEqualTo("Got 1 widgets.\n"); + } +} diff --git a/appengine/datastore/indexes-perfect/pom.xml b/appengine/datastore/indexes-perfect/pom.xml new file mode 100644 index 00000000000..14fab365685 --- /dev/null +++ b/appengine/datastore/indexes-perfect/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-datastore-indexes-perfect + + com.google.cloud + doc-samples + 1.0.0 + ../../.. + + + + com.google.appengine + appengine-api-1.0-sdk + ${appengine.sdk.version} + + + javax.servlet + servlet-api + jar + provided + + + + + junit + junit + 4.10 + 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 + + + com.google.truth + truth + 0.28 + test + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + + com.google.appengine + appengine-maven-plugin + ${appengine.sdk.version} + + + + diff --git a/appengine/datastore/indexes-perfect/src/main/java/com/example/appengine/IndexesServlet.java b/appengine/datastore/indexes-perfect/src/main/java/com/example/appengine/IndexesServlet.java new file mode 100644 index 00000000000..0dbca3ce307 --- /dev/null +++ b/appengine/datastore/indexes-perfect/src/main/java/com/example/appengine/IndexesServlet.java @@ -0,0 +1,98 @@ +/* + * 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.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * A servlet to demonstrate the use of Cloud Datastore indexes. + */ +public class IndexesServlet extends HttpServlet { + private final DatastoreService datastore; + + public IndexesServlet() { + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + PrintWriter out = resp.getWriter(); + // These queries should all work with the same index. + // [START queries_and_indexes_example_1] + Query q1 = + new Query("Person") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("lastName", FilterOperator.EQUAL, "Smith"), + new FilterPredicate("height", FilterOperator.EQUAL, 72))) + .addSort("height", Query.SortDirection.DESCENDING); + // [END queries_and_indexes_example_1] + List r1 = datastore.prepare(q1).asList(FetchOptions.Builder.withDefaults()); + out.printf("Got %d results from query 1.\n", r1.size()); + + // [START queries_and_indexes_example_2] + Query q2 = + new Query("Person") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("lastName", FilterOperator.EQUAL, "Jones"), + new FilterPredicate("height", FilterOperator.EQUAL, 63))) + .addSort("height", Query.SortDirection.DESCENDING); + // [END queries_and_indexes_example_2] + List r2 = datastore.prepare(q2).asList(FetchOptions.Builder.withDefaults()); + out.printf("Got %d results from query 2.\n", r2.size()); + + // [START queries_and_indexes_example_3] + Query q3 = + new Query("Person") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("lastName", FilterOperator.EQUAL, "Friedkin"), + new FilterPredicate("firstName", FilterOperator.EQUAL, "Damian"))) + .addSort("height", Query.SortDirection.ASCENDING); + // [END queries_and_indexes_example_3] + List r3 = datastore.prepare(q3).asList(FetchOptions.Builder.withDefaults()); + out.printf("Got %d results from query 3.\n", r3.size()); + + // [START queries_and_indexes_example_4] + Query q4 = + new Query("Person") + .setFilter(new FilterPredicate("lastName", FilterOperator.EQUAL, "Blair")) + .addSort("firstName", Query.SortDirection.ASCENDING) + .addSort("height", Query.SortDirection.ASCENDING); + // [END queries_and_indexes_example_4] + List r4 = datastore.prepare(q4).asList(FetchOptions.Builder.withDefaults()); + out.printf("Got %d results from query 4.\n", r4.size()); + } +} diff --git a/appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..e9d8b21cb8f --- /dev/null +++ b/appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,21 @@ + + + + YOUR-PROJECT-ID + YOUR-VERSION-ID + true + diff --git a/appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/datastore-indexes.xml b/appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/datastore-indexes.xml new file mode 100644 index 00000000000..bb56fb4bf50 --- /dev/null +++ b/appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/datastore-indexes.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/web.xml b/appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..049fd7a05e7 --- /dev/null +++ b/appengine/datastore/indexes-perfect/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,39 @@ + + + + + indexes-servlet + com.example.appengine.IndexesServlet + + + indexes-servlet + / + + + + + profile + /* + + + CONFIDENTIAL + + + diff --git a/appengine/datastore/indexes-perfect/src/test/java/com/example/appengine/IndexesServletTest.java b/appengine/datastore/indexes-perfect/src/test/java/com/example/appengine/IndexesServletTest.java new file mode 100644 index 00000000000..e82f5b302ab --- /dev/null +++ b/appengine/datastore/indexes-perfect/src/test/java/com/example/appengine/IndexesServletTest.java @@ -0,0 +1,83 @@ +/* + * 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.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +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.io.PrintWriter; +import java.io.StringWriter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Unit tests for {@link IndexesServlet}. + */ +@RunWith(JUnit4.class) +public class IndexesServletTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting#Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + @Mock private HttpServletRequest mockRequest; + @Mock private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private IndexesServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + helper.setUp(); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new IndexesServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void doGet_emptyDatastore_writesNoWidgets() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + String response = responseWriter.toString(); + assertThat(response).contains("Got 0 results from query 1."); + assertThat(response).contains("Got 0 results from query 2."); + assertThat(response).contains("Got 0 results from query 3."); + assertThat(response).contains("Got 0 results from query 4."); + } +} diff --git a/appengine/datastore/indexes/pom.xml b/appengine/datastore/indexes/pom.xml new file mode 100644 index 00000000000..b6c95b9a3dd --- /dev/null +++ b/appengine/datastore/indexes/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.appengine + appengine-datastore-indexes + + com.google.cloud + doc-samples + 1.0.0 + ../../.. + + + + com.google.appengine + appengine-api-1.0-sdk + ${appengine.sdk.version} + + + javax.servlet + servlet-api + jar + provided + + + + + junit + junit + 4.10 + 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 + + + com.google.truth + truth + 0.28 + test + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + + com.google.appengine + appengine-maven-plugin + ${appengine.sdk.version} + + + + diff --git a/appengine/datastore/indexes/src/main/java/com/example/appengine/IndexesServlet.java b/appengine/datastore/indexes/src/main/java/com/example/appengine/IndexesServlet.java new file mode 100644 index 00000000000..78605f64bf2 --- /dev/null +++ b/appengine/datastore/indexes/src/main/java/com/example/appengine/IndexesServlet.java @@ -0,0 +1,64 @@ +/* + * 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.appengine; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.CompositeFilterOperator; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * A servlet to demonstrate the use of Cloud Datastore indexes. + */ +public class IndexesServlet extends HttpServlet { + private final DatastoreService datastore; + + public IndexesServlet() { + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + // [START exploding_index_example_1] + Query q = + new Query("Widget") + .setFilter( + CompositeFilterOperator.and( + new FilterPredicate("x", FilterOperator.EQUAL, 1), + new FilterPredicate("y", FilterOperator.EQUAL, 2))) + .addSort("date", Query.SortDirection.ASCENDING); + // [END exploding_index_example_1] + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + + PrintWriter out = resp.getWriter(); + out.printf("Got %d widgets.\n", results.size()); + } +} diff --git a/appengine/datastore/indexes/src/main/webapp/WEB-INF/appengine-web.xml b/appengine/datastore/indexes/src/main/webapp/WEB-INF/appengine-web.xml new file mode 100644 index 00000000000..e9d8b21cb8f --- /dev/null +++ b/appengine/datastore/indexes/src/main/webapp/WEB-INF/appengine-web.xml @@ -0,0 +1,21 @@ + + + + YOUR-PROJECT-ID + YOUR-VERSION-ID + true + diff --git a/appengine/datastore/indexes/src/main/webapp/WEB-INF/datastore-indexes.xml b/appengine/datastore/indexes/src/main/webapp/WEB-INF/datastore-indexes.xml new file mode 100644 index 00000000000..106de35043d --- /dev/null +++ b/appengine/datastore/indexes/src/main/webapp/WEB-INF/datastore-indexes.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/appengine/datastore/indexes/src/main/webapp/WEB-INF/web.xml b/appengine/datastore/indexes/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..049fd7a05e7 --- /dev/null +++ b/appengine/datastore/indexes/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,39 @@ + + + + + indexes-servlet + com.example.appengine.IndexesServlet + + + indexes-servlet + / + + + + + profile + /* + + + CONFIDENTIAL + + + diff --git a/appengine/datastore/indexes/src/test/java/com/example/appengine/IndexesServletTest.java b/appengine/datastore/indexes/src/test/java/com/example/appengine/IndexesServletTest.java new file mode 100644 index 00000000000..daf6e8f3821 --- /dev/null +++ b/appengine/datastore/indexes/src/test/java/com/example/appengine/IndexesServletTest.java @@ -0,0 +1,77 @@ +/* + * 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.appengine; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +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.io.PrintWriter; +import java.io.StringWriter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Unit tests for {@link IndexesServlet}. + */ +@RunWith(JUnit4.class) +public class IndexesServletTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()); + + @Mock private HttpServletRequest mockRequest; + @Mock private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private IndexesServlet servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + helper.setUp(); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + servletUnderTest = new IndexesServlet(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void doGet_emptyDatastore_writesNoWidgets() throws Exception { + servletUnderTest.doGet(mockRequest, mockResponse); + + assertThat(responseWriter.toString()) + .named("IndexesServlet response") + .isEqualTo("Got 0 widgets.\n"); + } +} diff --git a/appengine/datastore/src/test/java/com/example/appengine/IndexesTest.java b/appengine/datastore/src/test/java/com/example/appengine/IndexesTest.java new file mode 100644 index 00000000000..6a3e616bb5a --- /dev/null +++ b/appengine/datastore/src/test/java/com/example/appengine/IndexesTest.java @@ -0,0 +1,103 @@ +/* + * 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.appengine; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.FetchOptions; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.appengine.api.datastore.Query.FilterOperator; +import com.google.appengine.api.datastore.Query.FilterPredicate; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.common.collect.ImmutableList; +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; + +/** + * Unit tests to demonstrate App Engine Datastore queries. + */ +@RunWith(JUnit4.class) +public class IndexesTest { + + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper( + // Set no eventual consistency, that way queries return all results. + // https://cloud.google.com/appengine/docs/java/tools/localunittesting#Java_Writing_High_Replication_Datastore_tests + new LocalDatastoreServiceTestConfig() + .setDefaultHighRepJobPolicyUnappliedJobPercentage(0)); + + private DatastoreService datastore; + + @Before + public void setUp() { + helper.setUp(); + datastore = DatastoreServiceFactory.getDatastoreService(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void propertyFilterExample_returnsMatchingEntities() throws Exception { + // [START unindexed_properties_1] + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + + Key acmeKey = KeyFactory.createKey("Company", "Acme"); + + Entity tom = new Entity("Person", "Tom", acmeKey); + tom.setProperty("name", "Tom"); + tom.setProperty("age", 32); + datastore.put(tom); + + Entity lucy = new Entity("Person", "Lucy", acmeKey); + lucy.setProperty("name", "Lucy"); + lucy.setUnindexedProperty("age", 29); + datastore.put(lucy); + + Filter ageFilter = new FilterPredicate("age", FilterOperator.GREATER_THAN, 25); + + Query q = new Query("Person").setAncestor(acmeKey).setFilter(ageFilter); + + // Returns tom but not lucy, because her age is unindexed + List results = datastore.prepare(q).asList(FetchOptions.Builder.withDefaults()); + // [END unindexed_properties_1] + + assertThat(getKeys(results)).named("query result keys").containsExactly(tom.getKey()); + } + + private ImmutableList getKeys(List entities) { + ImmutableList.Builder keys = ImmutableList.builder(); + for (Entity entity : entities) { + keys.add(entity.getKey()); + } + return keys.build(); + } +} diff --git a/java-repo-tools/test-devserver.sh b/java-repo-tools/test-devserver.sh new file mode 100755 index 00000000000..62b3dfefef7 --- /dev/null +++ b/java-repo-tools/test-devserver.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# 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. + +# Usage: +# test-devserver.sh path/to/project +# +# This script runs the local appengine:devserver Maven plugin and verifies that +# a request to http://localhost:8080/ does not return an error code. +# +# As an example, this is useful for verifying that datastore-indexes.xml is +# correct (only if autoGenerate=false and the / handler does all queries used), +# as an example. + +set -e +set -x + +if [ -z "$1" ]; then + echo "Missing directory parameter." + echo "Usage:" + echo " $0 path/to/project" + exit 1 +fi + +( +cd "$1" +expect -c ' + spawn mvn --batch-mode clean appengine:devserver -DskipTests + set timeout 600 + expect localhost:8080 + sleep 10 + spawn curl --silent --output /dev/stderr --write-out "%{http_code}" http://localhost:8080/ + expect { + "200" { + exit + } + } + exit 1 + ' +) + diff --git a/pom.xml b/pom.xml index e37c1554d2d..bd885080d87 100644 --- a/pom.xml +++ b/pom.xml @@ -47,6 +47,9 @@ appengine/appidentity appengine/channel appengine/datastore + appengine/datastore/indexes + appengine/datastore/indexes-exploding + appengine/datastore/indexes-perfect appengine/guestbook-objectify appengine/helloworld appengine/logs diff --git a/travis.sh b/travis.sh index fe0b680e9d9..7065ce69a80 100755 --- a/travis.sh +++ b/travis.sh @@ -24,3 +24,13 @@ if [ -z "$GOOGLE_APPLICATION_CREDENTIALS"]; then fi mvn --batch-mode clean verify -DskipTests=$SKIP_TESTS | egrep -v "(^\[INFO\] Download|^\[INFO\].*skipping)" +# Run tests using App Engine local devserver. +devserver_tests=( + appengine/datastore/indexes + appengine/datastore/indexes-exploding + appengine/datastore/indexes-perfect +) +for testdir in ${devserver_tests[@]} ; do + ./java-repo-tools/test-devserver.sh "${testdir}" +done +