From 367e7bd59a90518dd32430f876f7c9070abad8af Mon Sep 17 00:00:00 2001 From: Will Hayworth Date: Wed, 26 Oct 2016 14:49:33 -0700 Subject: [PATCH 1/3] Add a Guestbook example for the standard environment that uses the Cloud Datastore Java API. --- appengine/guestbook-cloud-datastore/README.md | 28 +++++ appengine/guestbook-cloud-datastore/pom.xml | 112 +++++++++++++++++ .../java/com/example/guestbook/Greeting.java | 117 ++++++++++++++++++ .../java/com/example/guestbook/Guestbook.java | 69 +++++++++++ .../com/example/guestbook/Persistence.java | 45 +++++++ .../guestbook/SignGuestbookServlet.java | 52 ++++++++ .../src/main/webapp/WEB-INF/appengine-web.xml | 10 ++ .../src/main/webapp/WEB-INF/index.yaml | 7 ++ .../main/webapp/WEB-INF/logging.properties | 13 ++ .../src/main/webapp/WEB-INF/web.xml | 22 ++++ .../src/main/webapp/guestbook.jsp | 98 +++++++++++++++ .../src/main/webapp/stylesheets/main.css | 4 + .../com/example/guestbook/GreetingTest.java | 51 ++++++++ .../guestbook/SignGuestbookServletTest.java | 73 +++++++++++ .../java/com/example/guestbook/TestUtils.java | 45 +++++++ 15 files changed, 746 insertions(+) create mode 100644 appengine/guestbook-cloud-datastore/README.md create mode 100644 appengine/guestbook-cloud-datastore/pom.xml create mode 100644 appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Greeting.java create mode 100644 appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Guestbook.java create mode 100644 appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/Persistence.java create mode 100644 appengine/guestbook-cloud-datastore/src/main/java/com/example/guestbook/SignGuestbookServlet.java create mode 100644 appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/appengine-web.xml create mode 100644 appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/index.yaml create mode 100644 appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/logging.properties create mode 100644 appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/web.xml create mode 100644 appengine/guestbook-cloud-datastore/src/main/webapp/guestbook.jsp create mode 100644 appengine/guestbook-cloud-datastore/src/main/webapp/stylesheets/main.css create mode 100644 appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/GreetingTest.java create mode 100644 appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/SignGuestbookServletTest.java create mode 100644 appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/TestUtils.java 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)}'.

+<% + // Look at all of our greetings + for (Greeting greeting : greetings) { + pageContext.setAttribute("greeting_content", greeting.content); + String author; + if (greeting.authorEmail == null) { + author = "An anonymous person"; + } else { + author = greeting.authorEmail; + String author_id = greeting.authorId; + if (user != null && user.getUserId().equals(author_id)) { + author += " (You)"; + } + } + pageContext.setAttribute("greeting_user", author); +%> +

${fn:escapeXml(greeting_user)} wrote:

+
${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..8b3eed2451d --- /dev/null +++ b/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/GreetingTest.java @@ -0,0 +1,51 @@ +/** + * 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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@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 greetings = guestbook.getGreetings(); + assertTrue(greetings.size() == 1); + assertEquals(greeting, greetings.get(0)); + } + + @After + public void tearDown() { + TestUtils.stopDatastore(); + } +} diff --git a/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/SignGuestbookServletTest.java b/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/SignGuestbookServletTest.java new file mode 100644 index 00000000000..03aaa73bff6 --- /dev/null +++ b/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/SignGuestbookServletTest.java @@ -0,0 +1,73 @@ +/** + * 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 org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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; + +@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 greetings = guestbook.getGreetings(); + + assertTrue(greetings.size() == 1); + assertTrue(greetings.get(0).content.equals(testGreeting)); + } + + @After + public void tearDown() { + TestUtils.stopDatastore(); + } +} diff --git a/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/TestUtils.java b/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/TestUtils.java new file mode 100644 index 00000000000..7a9716e467b --- /dev/null +++ b/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/TestUtils.java @@ -0,0 +1,45 @@ +package com.example.guestbook; + +import static com.example.guestbook.Persistence.getDatastore; + +import com.google.cloud.datastore.Datastore; +import com.google.cloud.datastore.Key; +import com.google.cloud.datastore.Query; +import com.google.cloud.datastore.QueryResults; +import com.google.cloud.datastore.testing.LocalDatastoreHelper; +import com.google.common.collect.Lists; + +import java.io.IOException; +import java.util.ArrayList; + +public class TestUtils { + static LocalDatastoreHelper datastore = LocalDatastoreHelper.create(); + + public static void startDatastore() { + try { + datastore.start(); + Persistence.setDatastore(datastore.options().service()); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void stopDatastore() { + try { + datastore.stop(); + Persistence.setDatastore(null); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static void wipeDatastore() { + Datastore datastore = getDatastore(); + QueryResults guestbooks = datastore.run(Query.keyQueryBuilder().kind("Greeting").build()); + ArrayList keys = Lists.newArrayList(guestbooks); + + if (!keys.isEmpty()) { + datastore.delete(keys.toArray(new Key[keys.size()])); + } + } +} From 02a8173d423084a6e334f9d30dd2931cab8bd0a1 Mon Sep 17 00:00:00 2001 From: Will Hayworth Date: Wed, 26 Oct 2016 17:25:39 -0700 Subject: [PATCH 2/3] Style fixes and corrections. --- appengine/guestbook-cloud-datastore/README.md | 4 +- .../java/com/example/guestbook/Greeting.java | 46 +++++++------ .../java/com/example/guestbook/Guestbook.java | 67 +++++++++++++------ .../com/example/guestbook/Persistence.java | 23 +++---- .../guestbook/SignGuestbookServlet.java | 20 +++--- .../src/main/webapp/WEB-INF/appengine-web.xml | 12 ++-- .../src/main/webapp/WEB-INF/index.yaml | 2 +- .../main/webapp/WEB-INF/logging.properties | 3 +- .../src/main/webapp/WEB-INF/web.xml | 34 +++++----- .../com/example/guestbook/GreetingTest.java | 19 +++--- .../guestbook/SignGuestbookServletTest.java | 23 +++---- 11 files changed, 138 insertions(+), 115 deletions(-) diff --git a/appengine/guestbook-cloud-datastore/README.md b/appengine/guestbook-cloud-datastore/README.md index 94b3edf3de9..1c6d6957c8a 100644 --- a/appengine/guestbook-cloud-datastore/README.md +++ b/appengine/guestbook-cloud-datastore/README.md @@ -19,10 +19,10 @@ Then start the [Cloud Datastore Emulator](https://cloud.google.com/datastore/doc 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 + mvn clean appengine:run ## Deploying Modify `appengine-web.xml` to reflect your app ID and version, then: - mvn clean appengine:update + mvn clean appengine:deploy 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 index 988118174c0..12eb3d6c6de 100644 --- 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 @@ -1,16 +1,14 @@ /** * 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 + *

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 + *

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 + *

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. */ @@ -25,6 +23,8 @@ 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; @@ -32,7 +32,6 @@ public class Greeting { private Guestbook book; public Key key; - public String authorEmail; public String authorId; public String content; @@ -54,11 +53,6 @@ public Greeting(String book, String content, String id, String 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; @@ -102,16 +96,28 @@ public boolean equals(Object o) { 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); + 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); } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("key", key) + .add("authorEmail", authorEmail) + .add("authorId", authorId) + .add("content", content) + .add("date", date) + .add("book", book) + .toString(); + } } -//[END all] \ No newline at end of file +//[END all] 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 index 694010ab5c5..ae407175ecb 100644 --- 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 @@ -1,19 +1,16 @@ /** * 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 + *

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 + *

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 + *

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; @@ -27,20 +24,25 @@ 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 kf = getKeyFactory(Guestbook.class); - + 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 = kf.newKey(this.book); // There is a 1:1 mapping between Guestbook names and Guestbook objects + key = + keyFactory.newKey( + this.book); // There is a 1:1 mapping between Guestbook names and Guestbook objects } public Key getKey() { @@ -49,12 +51,13 @@ public Key getKey() { 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(); + EntityQuery query = + Query.entityQueryBuilder() + .kind("Greeting") + .filter(hasAncestor(key)) + .orderBy(desc("date")) + .limit(5) + .build(); QueryResults results = getDatastore().run(query); @@ -65,5 +68,31 @@ public List getGreetings() { return resultListBuilder.build(); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Guestbook guestbook = (Guestbook) o; + return Objects.equals(book, guestbook.book) && Objects.equals(key, guestbook.key); + } + + @Override + public int hashCode() { + return Objects.hash(book, key); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("keyFactory", keyFactory) + .add("book", book) + .add("key", key) + .toString(); + } } -//[END all] \ No newline at end of file +//[END all] 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 index 3aee65c89cc..3dbc2285743 100644 --- 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 @@ -1,19 +1,16 @@ /** * 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 + *

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 + *

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 + *

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; @@ -34,12 +31,12 @@ public static Datastore getDatastore() { 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); } + + public static KeyFactory getKeyFactory(Class c) { + return getDatastore().newKeyFactory().kind(c.getSimpleName()); + } } //[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 index eec1a34c2a8..8d6061a7146 100644 --- 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 @@ -1,16 +1,14 @@ /** * 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 + *

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 + *

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 + *

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. */ @@ -20,6 +18,7 @@ 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; @@ -29,12 +28,11 @@ public class SignGuestbookServlet extends HttpServlet { // Process the HTTP POST of the form @Override - public void doPost(HttpServletRequest req, HttpServletResponse resp) - throws IOException { + 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. + User user = userService.getCurrentUser(); // Find out who the user is. String guestbookName = req.getParameter("guestbookName"); String content = req.getParameter("content"); 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 index 14ea1140aa7..bdcb11ebd86 100644 --- 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 @@ -1,10 +1,8 @@ - your-app-id-here - your-app-version-here - true - - - - + 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 index 03f9df1f728..e9beac04f79 100644 --- a/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/index.yaml +++ b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/index.yaml @@ -4,4 +4,4 @@ indexes: ancestor: yes properties: - name: date - direction: desc \ No newline at end of file + direction: desc 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 index a17206681f0..c2a1d42755d 100644 --- a/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/logging.properties +++ b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/logging.properties @@ -8,6 +8,5 @@ # # # - # Set the default logging level for all loggers to WARNING -.level = 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 index 1ffb6bb5853..110a5c42fbc 100644 --- a/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/web.xml +++ b/appengine/guestbook-cloud-datastore/src/main/webapp/WEB-INF/web.xml @@ -1,22 +1,22 @@ - + - - - sign - com.example.guestbook.SignGuestbookServlet - 1 - + + + sign + com.example.guestbook.SignGuestbookServlet + 1 + - - sign - /sign - + + sign + /sign + - - guestbook.jsp - - + + guestbook.jsp + + 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 index 8b3eed2451d..6ddaa732d37 100644 --- 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 @@ -1,16 +1,14 @@ /** * 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 + *

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 + *

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 + *

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; @@ -18,16 +16,17 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.util.List; - 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(); diff --git a/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/SignGuestbookServletTest.java b/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/SignGuestbookServletTest.java index 03aaa73bff6..243fca2a858 100644 --- a/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/SignGuestbookServletTest.java +++ b/appengine/guestbook-cloud-datastore/src/test/java/com/example/guestbook/SignGuestbookServletTest.java @@ -1,29 +1,22 @@ /** * 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 + *

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 + *

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 + *

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 java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -32,6 +25,10 @@ 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(); From 3778448b0bf26d81796935438f66829af1ef7560 Mon Sep 17 00:00:00 2001 From: Will Hayworth Date: Wed, 26 Oct 2016 17:48:47 -0700 Subject: [PATCH 3/3] Remove extraneous (old Maven plugin) parameters. --- appengine/guestbook-cloud-datastore/pom.xml | 193 +++++++++----------- 1 file changed, 91 insertions(+), 102 deletions(-) diff --git a/appengine/guestbook-cloud-datastore/pom.xml b/appengine/guestbook-cloud-datastore/pom.xml index d40dafdede5..bc7683efe06 100644 --- a/appengine/guestbook-cloud-datastore/pom.xml +++ b/appengine/guestbook-cloud-datastore/pom.xml @@ -1,112 +1,101 @@ - + - 4.0.0 - war - 1.0-SNAPSHOT + 4.0.0 + war + 1.0-SNAPSHOT - com.example.appengine - appengine-guestbook-cloud-datastore - - 19.0 - - - com.google.cloud - doc-samples - 1.0.0 - ../.. - + com.example.appengine + appengine-guestbook-cloud-datastore + + 19.0 + + + com.google.cloud + doc-samples + 1.0.0 + ../.. + - - - 3.3.9 - - + + + 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.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.cloud + google-cloud + 0.4.0 + - - com.google.guava - guava - ${guava.version} - + + 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 - - + + + 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 - - - - - - - - + + + + com.google.cloud.tools + appengine-maven-plugin + 1.0.0 + + +