From 0722c030e53e180b88ca07e64ac715de0081d955 Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Tue, 22 Nov 2016 13:57:51 -0800 Subject: [PATCH 01/24] Adding base code for PubSub example --- flexible/pubsub/pom.xml | 80 ++++++++ flexible/pubsub/src/main/appengine/app.yaml | 16 ++ .../example/managedvms/pubsub/PubSubHome.java | 78 ++++++++ .../managedvms/pubsub/PubSubPublish.java | 33 ++++ .../example/managedvms/pubsub/PubSubPush.java | 172 ++++++++++++++++++ 5 files changed, 379 insertions(+) create mode 100644 flexible/pubsub/pom.xml create mode 100644 flexible/pubsub/src/main/appengine/app.yaml create mode 100644 flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubHome.java create mode 100644 flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPublish.java create mode 100644 flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPush.java diff --git a/flexible/pubsub/pom.xml b/flexible/pubsub/pom.xml new file mode 100644 index 00000000000..cb25c4231bb --- /dev/null +++ b/flexible/pubsub/pom.xml @@ -0,0 +1,80 @@ + + + 4.0.0 + war + 1.0-SNAPSHOT + com.example.managedvms + managed-vms-pubsub + + + doc-samples + com.google.cloud + 1.0.0 + ../.. + + + + 1.8 + 1.8 + + 1.0.0 + 9.3.8.v20160314 + + false + + + + + javax.servlet + javax.servlet-api + 3.1.0 + jar + provided + + + com.google.cloud + google-cloud-datastore + 0.6.0 + + + + com.google.cloud + google-cloud-pubsub + 0.6.0 + + + + + + ${project.build.directory}/${project.build.finalName}/WEB-INF/classes + + + com.google.cloud.tools + appengine-maven-plugin + ${appengine.maven.plugin} + + + + + + org.eclipse.jetty + jetty-maven-plugin + ${jetty.maven.plugin} + + + + diff --git a/flexible/pubsub/src/main/appengine/app.yaml b/flexible/pubsub/src/main/appengine/app.yaml new file mode 100644 index 00000000000..1735fe7a5b7 --- /dev/null +++ b/flexible/pubsub/src/main/appengine/app.yaml @@ -0,0 +1,16 @@ +runtime: java +env: flex + +handlers: +- url: /.* + script: this field is required, but ignored + secure: always + +# [START env_variables] +env_variables: + PUBSUB_TOPIC: + # This token is used to verify that requests originate from your + # application. It can be any sufficiently random string. + PUBSUB_VERIFICATION_TOKEN: +# [END env_variables] + diff --git a/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubHome.java b/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubHome.java new file mode 100644 index 00000000000..73b2525a99a --- /dev/null +++ b/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubHome.java @@ -0,0 +1,78 @@ +package com.example.managedvms.pubsub; + +import com.google.cloud.datastore.Datastore; +import com.google.cloud.datastore.DatastoreOptions; +import com.google.cloud.datastore.Entity; +import com.google.cloud.datastore.KeyFactory; +import com.google.cloud.datastore.Query; +import com.google.cloud.datastore.QueryResults; +import com.google.cloud.datastore.StructuredQuery; +import com.google.gson.Gson; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet(name = "main", value = "/") +public class PubSubHome extends HttpServlet{ + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + // Prepare to present list + resp.setContentType("text/html"); + PrintWriter out = resp.getWriter(); + out.println("\n" + + "\n" + + "\n" + + "Send a PubSub Message\n" + + "\n" + + ""); + + // Get Messages + ArrayList messageList = getMessages(); + + // Display received messages + out.println("Received Messages:
"); + for (String message : messageList) { + out.printf("%s
\n", message); + } + + // Add Form to publish a new message + out.println("
\n" + + "\n" + + "\n" + + "\n" + + "
"); + + // Finish HTML Response + out.println("\n" + + ""); + } + + private ArrayList getMessages() { + // Get Message saved in Datastore + Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); + Query query = Query.newEntityQueryBuilder() + .setKind("pushed_messages") + .setLimit(1) + .build(); + QueryResults results = datastore.run(query); + + // Convert stored JSON into ArrayList + ArrayList messageList = new ArrayList<>(); + Gson gson = new Gson(); + if (results.hasNext()) { + Entity entity = results.next(); + messageList = gson.fromJson(entity.getString("messages"), + messageList.getClass()); + } + + return messageList; + } +} diff --git a/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPublish.java b/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPublish.java new file mode 100644 index 00000000000..2b3a7d5a052 --- /dev/null +++ b/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPublish.java @@ -0,0 +1,33 @@ +package com.example.managedvms.pubsub; + +import com.google.cloud.pubsub.Message; +import com.google.cloud.pubsub.PubSub; +import com.google.cloud.pubsub.PubSubOptions; +import com.google.cloud.pubsub.Topic; +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet(name = "PubSubPublish", value = "/publish") +public class PubSubPublish extends HttpServlet { + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, + ServletException { + // Load environment variables + final String topicName = System.getenv("PUBSUB_TOPIC"); + final String payload = req.getParameter("payload"); + + // Create a PubSub Service and get Topic + PubSub pubsub = PubSubOptions.getDefaultInstance().getService(); + Topic topic = pubsub.getTopic(topicName); + + // Publish Message + topic.publish(Message.of(payload)); + + // redirect "/" + resp.sendRedirect("/"); + } +} diff --git a/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPush.java new file mode 100644 index 00000000000..a1b50132f7f --- /dev/null +++ b/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPush.java @@ -0,0 +1,172 @@ +package com.example.managedvms.pubsub; + +import com.google.cloud.datastore.BlobValue; +import com.google.cloud.datastore.Datastore; +import com.google.cloud.datastore.DatastoreOptions; +import com.google.cloud.datastore.DateTime; +import com.google.cloud.datastore.Entity; +import com.google.cloud.datastore.FullEntity; +import com.google.cloud.datastore.IncompleteKey; +import com.google.cloud.datastore.Key; +import com.google.cloud.datastore.KeyFactory; +import com.google.cloud.datastore.Transaction; +import com.google.cloud.datastore.Value; +import com.google.cloud.datastore.ValueBuilder; +import com.google.cloud.pubsub.Message; +import com.google.cloud.pubsub.PubSub; +import com.google.cloud.pubsub.PubSubOptions; +import com.google.cloud.pubsub.Topic; +import com.google.cloud.pubsub.TopicInfo; +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet(name = "PubSubPush", value = "/pubsub/push") +public class PubSubPush extends HttpServlet { + private Datastore datastoreService = null; + + @Override + public void init(ServletConfig config) throws ServletException { + // Initialize + datastoreService = DatastoreOptions.getDefaultInstance().getService(); + + // Prepare message list if it's empty + while (true) { + if (createMessageList()) { + break; + } + } + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, + ServletException { + final String apiToken = System.getenv("PUBSUB_VERIFICATION_TOKEN"); + final String topicName = System.getenv("PUBSUB_TOPIC"); + + try { + // message = JSON.parse request.body.read + JsonReader jsonReader = new JsonReader(req.getReader()); + + // Token doesn't match apiToken + if (req.getParameter("token").compareTo(apiToken) != 0) { + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + Map> responseBody = new Gson().fromJson(jsonReader, Map.class); + final String data = responseBody.get("message").get("data"); + + // Ugly... + byte[] interm = Base64.getDecoder().decode(data); + String payload = new String(interm, StandardCharsets.UTF_8); + + // Save payload to be displayed later + saveMessage(payload); + } catch (JsonParseException error) { + resp.getWriter().print(error.toString()); + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + } + } + + private boolean createMessageList() { + // Start a new transaction + Transaction transaction = datastoreService.newTransaction(); + + // Create a Gson object to serialize messages ArrayList as a JSON string + Gson gson = new Gson(); + + try { + // Create a keyfactory for entries of kind pushed_messages + KeyFactory keyFactory = datastoreService.newKeyFactory().setKind("pushed_messages"); + + // Lookup message_list + Key key = keyFactory.newKey("message_list"); + Entity entity = transaction.get(key); + + // Entity doesn't exist so let's create it! + if (entity == null) { + ArrayList messages = new ArrayList<>(); + entity = Entity.newBuilder(key) + .set("messages", gson.toJson(messages)) + .build(); + transaction.put(entity); + transaction.commit(); + } else { + return true; + } + } catch (Exception err) { + System.err.println(""+err); + } finally { + if (transaction.isActive()) { + transaction.rollback(); + // we don't have an entry yet transaction failed + return false; + } + } + // we have an entry to work with + return true; + } + + private void saveMessage(String message) { + // Attempt to save message + while (true) { + if (trySaveMessage(message)) { + break; + } + } + } + + private boolean trySaveMessage(String message) { + // Start a new transaction + Transaction transaction = datastoreService.newTransaction(); + + // Create a Gson object to parse and serialize an ArrayList + Gson gson = new Gson(); + + try { + // Lookup pushed_messages + KeyFactory keyFactory = datastoreService.newKeyFactory().setKind("pushed_messages"); + Key key = keyFactory.newKey("message_list"); + Entity entity = transaction.get(key); + + // Parse JSON into an ArrayList + ArrayList messages = null; + messages = gson.fromJson(entity.getString("messages"), messages.getClass()); + + // Add new message and save updated entry + messages.add(message); + entity = Entity.newBuilder(entity) + .set("messages", gson.toJson(messages)) + .build(); + transaction.update(entity); + transaction.commit(); + } finally { + if (transaction.isActive()) { + transaction.rollback(); + // didn't work out try again + return false; + } + } + // It saved the new entry! + return true; + } +} + From 3baa2d94c0b00a281f7c01e7380a9e5cbeef9616 Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Tue, 29 Nov 2016 19:19:57 -0800 Subject: [PATCH 02/24] Changed path from managedvms to appengine --- .../com/example/{managedvms => appengine}/pubsub/PubSubHome.java | 0 .../example/{managedvms => appengine}/pubsub/PubSubPublish.java | 0 .../com/example/{managedvms => appengine}/pubsub/PubSubPush.java | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename flexible/pubsub/src/main/java/com/example/{managedvms => appengine}/pubsub/PubSubHome.java (100%) rename flexible/pubsub/src/main/java/com/example/{managedvms => appengine}/pubsub/PubSubPublish.java (100%) rename flexible/pubsub/src/main/java/com/example/{managedvms => appengine}/pubsub/PubSubPush.java (100%) diff --git a/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubHome.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java similarity index 100% rename from flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubHome.java rename to flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java diff --git a/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPublish.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPublish.java similarity index 100% rename from flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPublish.java rename to flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPublish.java diff --git a/flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java similarity index 100% rename from flexible/pubsub/src/main/java/com/example/managedvms/pubsub/PubSubPush.java rename to flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java From 33fd59788c21ea725a182229acf4ec70adec0ba3 Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Tue, 29 Nov 2016 19:21:59 -0800 Subject: [PATCH 03/24] Updating code style and added exponential backoff timeout --- .../example/appengine/pubsub/PubSubHome.java | 22 ++- .../appengine/pubsub/PubSubPublish.java | 5 +- .../example/appengine/pubsub/PubSubPush.java | 126 ++++++++++-------- 3 files changed, 84 insertions(+), 69 deletions(-) diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java index 73b2525a99a..707b1de1ed3 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java +++ b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java @@ -1,17 +1,15 @@ -package com.example.managedvms.pubsub; +package com.example.appengine.pubsub; import com.google.cloud.datastore.Datastore; import com.google.cloud.datastore.DatastoreOptions; import com.google.cloud.datastore.Entity; -import com.google.cloud.datastore.KeyFactory; import com.google.cloud.datastore.Query; import com.google.cloud.datastore.QueryResults; -import com.google.cloud.datastore.StructuredQuery; import com.google.gson.Gson; + import java.io.IOException; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.List; +import java.util.LinkedList; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; @@ -19,10 +17,10 @@ import javax.servlet.http.HttpServletResponse; @WebServlet(name = "main", value = "/") -public class PubSubHome extends HttpServlet{ +public class PubSubHome extends HttpServlet { @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) - throws IOException, ServletException { + throws IOException, ServletException { // Prepare to present list resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); @@ -35,12 +33,12 @@ public void doGet(HttpServletRequest req, HttpServletResponse resp) + ""); // Get Messages - ArrayList messageList = getMessages(); + LinkedList messageList = getMessages(); // Display received messages out.println("Received Messages:
"); for (String message : messageList) { - out.printf("%s
\n", message); + out.printf("%s
\n", message); } // Add Form to publish a new message @@ -55,7 +53,7 @@ public void doGet(HttpServletRequest req, HttpServletResponse resp) + ""); } - private ArrayList getMessages() { + private LinkedList getMessages() { // Get Message saved in Datastore Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); Query query = Query.newEntityQueryBuilder() @@ -64,8 +62,8 @@ private ArrayList getMessages() { .build(); QueryResults results = datastore.run(query); - // Convert stored JSON into ArrayList - ArrayList messageList = new ArrayList<>(); + // Convert stored JSON into LinkedList + LinkedList messageList = new LinkedList<>(); Gson gson = new Gson(); if (results.hasNext()) { Entity entity = results.next(); diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPublish.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPublish.java index 2b3a7d5a052..df6ea9c65fa 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPublish.java +++ b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPublish.java @@ -1,9 +1,10 @@ -package com.example.managedvms.pubsub; +package com.example.appengine.pubsub; import com.google.cloud.pubsub.Message; import com.google.cloud.pubsub.PubSub; import com.google.cloud.pubsub.PubSubOptions; import com.google.cloud.pubsub.Topic; + import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; @@ -22,7 +23,7 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOEx // Create a PubSub Service and get Topic PubSub pubsub = PubSubOptions.getDefaultInstance().getService(); - Topic topic = pubsub.getTopic(topicName); + Topic topic = pubsub.getTopic(topicName); // Publish Message topic.publish(Message.of(payload)); diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java index a1b50132f7f..fb834703538 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java +++ b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java @@ -1,36 +1,23 @@ -package com.example.managedvms.pubsub; +package com.example.appengine.pubsub; + +import static java.lang.Thread.sleep; -import com.google.cloud.datastore.BlobValue; import com.google.cloud.datastore.Datastore; import com.google.cloud.datastore.DatastoreOptions; -import com.google.cloud.datastore.DateTime; import com.google.cloud.datastore.Entity; -import com.google.cloud.datastore.FullEntity; -import com.google.cloud.datastore.IncompleteKey; import com.google.cloud.datastore.Key; import com.google.cloud.datastore.KeyFactory; import com.google.cloud.datastore.Transaction; -import com.google.cloud.datastore.Value; -import com.google.cloud.datastore.ValueBuilder; -import com.google.cloud.pubsub.Message; -import com.google.cloud.pubsub.PubSub; -import com.google.cloud.pubsub.PubSubOptions; -import com.google.cloud.pubsub.Topic; -import com.google.cloud.pubsub.TopicInfo; import com.google.gson.Gson; import com.google.gson.JsonParseException; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; -import java.io.BufferedReader; + import java.io.IOException; -import java.io.PrintWriter; -import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Base64; -import java.util.List; +import java.util.LinkedList; import java.util.Map; -import javax.servlet.Servlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; @@ -46,20 +33,12 @@ public class PubSubPush extends HttpServlet { public void init(ServletConfig config) throws ServletException { // Initialize datastoreService = DatastoreOptions.getDefaultInstance().getService(); - - // Prepare message list if it's empty - while (true) { - if (createMessageList()) { - break; - } - } } @Override - public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, - ServletException { + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { final String apiToken = System.getenv("PUBSUB_VERIFICATION_TOKEN"); - final String topicName = System.getenv("PUBSUB_TOPIC"); try { // message = JSON.parse request.body.read @@ -71,7 +50,8 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOEx return; } - Map> responseBody = new Gson().fromJson(jsonReader, Map.class); + Map> responseBody = new Gson() + .fromJson(jsonReader, Map.class); final String data = responseBody.get("message").get("data"); // Ugly... @@ -86,16 +66,57 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOEx } } + + + public void saveMessage(String message) { + new Thread(new Runnable() { + private final long maxSleepTime = 30000; // 30 seconds + + @Override + public void run() { + try { + // set starting sleepTime + long sleepTime = 1000; + // Prepare message list if it's empty + while (sleepTime < maxSleepTime) { + if (createMessageList()) { + break; + } + sleep(sleepTime); + // Exponential backoff + sleepTime *= 2; + } + + // reset starting sleepTime + sleepTime = 1000; + // Attempt to save message + while (sleepTime < maxSleepTime) { + if (trySaveMessage(message)) { + break; + } + sleep(sleepTime); + } + } catch (InterruptedException ie) { + System.err.println(ie); + } + } + }).start(); + } + private boolean createMessageList() { // Start a new transaction Transaction transaction = datastoreService.newTransaction(); - // Create a Gson object to serialize messages ArrayList as a JSON string + // Create a Gson object to serialize messages LinkedList as a JSON string Gson gson = new Gson(); + // Transaction flag + boolean messagesFound = false; + try { // Create a keyfactory for entries of kind pushed_messages - KeyFactory keyFactory = datastoreService.newKeyFactory().setKind("pushed_messages"); + KeyFactory keyFactory = datastoreService.newKeyFactory() + .setKind("pushed_messages"); // Lookup message_list Key key = keyFactory.newKey("message_list"); @@ -103,53 +124,48 @@ private boolean createMessageList() { // Entity doesn't exist so let's create it! if (entity == null) { - ArrayList messages = new ArrayList<>(); + LinkedList messages = new LinkedList<>(); entity = Entity.newBuilder(key) .set("messages", gson.toJson(messages)) .build(); transaction.put(entity); transaction.commit(); } else { - return true; + transaction.rollback(); } - } catch (Exception err) { - System.err.println(""+err); + messagesFound = true; } finally { if (transaction.isActive()) { - transaction.rollback(); // we don't have an entry yet transaction failed - return false; + transaction.rollback(); + messagesFound = false; } } // we have an entry to work with - return true; - } - - private void saveMessage(String message) { - // Attempt to save message - while (true) { - if (trySaveMessage(message)) { - break; - } - } + return messagesFound; } private boolean trySaveMessage(String message) { // Start a new transaction Transaction transaction = datastoreService.newTransaction(); - // Create a Gson object to parse and serialize an ArrayList + // Create a Gson object to parse and serialize an LinkedList Gson gson = new Gson(); + // Transaction flag + boolean messagesSaved = false; + try { // Lookup pushed_messages - KeyFactory keyFactory = datastoreService.newKeyFactory().setKind("pushed_messages"); + KeyFactory keyFactory = datastoreService.newKeyFactory() + .setKind("pushed_messages"); Key key = keyFactory.newKey("message_list"); Entity entity = transaction.get(key); - // Parse JSON into an ArrayList - ArrayList messages = null; - messages = gson.fromJson(entity.getString("messages"), messages.getClass()); + // Parse JSON into an LinkedList + LinkedList messages = gson.fromJson(entity.getString("messages"), + new TypeToken>() { + }.getType()); // Add new message and save updated entry messages.add(message); @@ -158,15 +174,15 @@ private boolean trySaveMessage(String message) { .build(); transaction.update(entity); transaction.commit(); + messagesSaved = true; } finally { if (transaction.isActive()) { transaction.rollback(); - // didn't work out try again - return false; + messagesSaved = false; } } // It saved the new entry! - return true; + return messagesSaved; } } From b674933d198f10e1de89130038474b8a3cac16f8 Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Tue, 29 Nov 2016 19:22:27 -0800 Subject: [PATCH 04/24] Added Test for PubSubHome --- .../appengine/pubsub/PubSubHomeTest.java | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java new file mode 100644 index 00000000000..ede9c70e2ff --- /dev/null +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java @@ -0,0 +1,151 @@ +package com.example.appengine.pubsub; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.cloud.datastore.Datastore; +import com.google.cloud.datastore.DatastoreOptions; +import com.google.cloud.datastore.Entity; +import com.google.cloud.datastore.Key; +import com.google.gson.Gson; +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.LinkedList; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@RunWith(JUnit4.class) +public class PubSubHomeTest { + private static final String FAKE_URL = "fakeurl.google"; + private static final String KIND = "pushed_messages"; + private static final String KEY = "message_list"; + private static final String FIELD = "messages"; + @Mock private HttpServletRequest mockRequest; + @Mock private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private PubSubHome servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + // Set up some fake HTTP requests + when(mockRequest.getRequestURI()).thenReturn(FAKE_URL); + + // Set up a fake HTTP response. + responseWriter = new StringWriter(); + when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); + + // Create an instance of the PubSubHome servlet + servletUnderTest = new PubSubHome(); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void doGet_OneMessage_IT() throws Exception { + LinkedList messageList = new LinkedList<>(); + messageList.add("Hello, World!"); + + // clear Message list + clearMessageList(); + + // Add messages to local Datastore + addMessages(messageList); + + // Insert messageList.. + servletUnderTest.doGet(mockRequest, mockResponse); + + // We expect our hello world response. + assertThat(responseWriter.toString()) + .named("Send a PubSub Message") + .contains(generateHtmlWithMessageList(messageList)); + + // clean up + clearMessageList(); + } + + @Test + public void doGet_MultipleMessages_IT() throws Exception { + LinkedList messageList = new LinkedList<>(); + messageList.add("Hello, World!"); + messageList.add("Hello, World!"); + + // clear Message list + clearMessageList(); + + // Add messages to local Datastore + addMessages(messageList); + + //Insert messageList + servletUnderTest.doGet(mockRequest, mockResponse); + + // Expect two Hello, World! messages + assertThat(responseWriter.toString()) + .named("Send a PubSub Message") + .contains(generateHtmlWithMessageList(messageList)); + + // clean up + clearMessageList(); + } + + private void clearMessageList() { + Datastore datastore = DatastoreOptions.getDefaultInstance() + .getService(); + + // Clear all message + Key pushedMessages = datastore.newKeyFactory().setKind(KIND).newKey(KEY); + datastore.delete(pushedMessages); + } + + private void addMessages(LinkedList messageList) { + Gson gson = new Gson(); + Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); + Key messages = datastore.newKeyFactory().setKind(KIND).newKey(KEY); + + // Create an entry with a list of messages + Entity entity = Entity.newBuilder(messages) + .set(FIELD, gson.toJson(messageList)) + .build(); + + datastore.put(entity); + } + + private String generateHtmlWithMessageList(LinkedList messageList) { + String html = "\n" + + "\n" + + "\n" + + "Send a PubSub Message\n" + + "\n" + + "\n" + + "Received Messages:
\n"; + + for (String message : messageList) { + html += message + "
\n"; + } + + // Add Form to publish a new message + html += "
\n" + + "\n" + + "\n" + + "\n" + + "
\n" + + "\n" + + "\n"; + + return html; + } + +} \ No newline at end of file From 79330cbf93efca62fbf2d1409cdde71acb3d25a4 Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Tue, 29 Nov 2016 19:26:15 -0800 Subject: [PATCH 05/24] Removed underscore before IT --- .../java/com/example/appengine/pubsub/PubSubHomeTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java index ede9c70e2ff..e547ab508e5 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java @@ -54,7 +54,7 @@ public void tearDown() throws Exception { } @Test - public void doGet_OneMessage_IT() throws Exception { + public void doGet_OneMessageIT() throws Exception { LinkedList messageList = new LinkedList<>(); messageList.add("Hello, World!"); @@ -77,7 +77,7 @@ public void doGet_OneMessage_IT() throws Exception { } @Test - public void doGet_MultipleMessages_IT() throws Exception { + public void doGet_MultipleMessagesIT() throws Exception { LinkedList messageList = new LinkedList<>(); messageList.add("Hello, World!"); messageList.add("Hello, World!"); From b4f932a9c82d3c18628791c3b19bb6eefb59fdae Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Tue, 29 Nov 2016 19:42:31 -0800 Subject: [PATCH 06/24] Suppressed abbreviation warning and removed whitespace --- .../test/java/com/example/appengine/pubsub/PubSubHomeTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java index e547ab508e5..a19bcfcdad6 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java @@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletResponse; @RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") public class PubSubHomeTest { private static final String FAKE_URL = "fakeurl.google"; private static final String KIND = "pushed_messages"; @@ -147,5 +148,4 @@ private String generateHtmlWithMessageList(LinkedList messageList) { return html; } - } \ No newline at end of file From 0a7781b03bd1e54ad0002462bedb9eff421086a9 Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Wed, 30 Nov 2016 09:20:56 -0800 Subject: [PATCH 07/24] Updating pom.xml with necessary plugins for tests * Updated timeout for PubSubPush --- flexible/pubsub/pom.xml | 46 ++++++++++ .../example/appengine/pubsub/PubSubPush.java | 91 +++++++++---------- 2 files changed, 87 insertions(+), 50 deletions(-) diff --git a/flexible/pubsub/pom.xml b/flexible/pubsub/pom.xml index cb25c4231bb..46fb222c90b 100644 --- a/flexible/pubsub/pom.xml +++ b/flexible/pubsub/pom.xml @@ -28,6 +28,7 @@ + 1.8 1.8 @@ -38,6 +39,11 @@ + + com.google.appengine + appengine-api-1.0-sdk + 1.9.38 + javax.servlet javax.servlet-api @@ -50,6 +56,7 @@ google-cloud-datastore 0.6.0 + com.google.cloud @@ -57,6 +64,45 @@ 0.6.0 + + + + com.google.appengine + appengine-testing + 1.9.38 + test + + + com.google.appengine + appengine-api-stubs + 1.9.38 + test + + + com.google.appengine + appengine-tools-sdk + 1.9.38 + test + + + + com.google.truth + truth + 0.30 + test + + + + junit + junit + test + + + org.mockito + mockito-all + 1.10.19 + test + diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java index fb834703538..5117350e165 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java +++ b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java @@ -18,7 +18,6 @@ import java.util.Base64; import java.util.LinkedList; import java.util.Map; -import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; @@ -27,12 +26,10 @@ @WebServlet(name = "PubSubPush", value = "/pubsub/push") public class PubSubPush extends HttpServlet { - private Datastore datastoreService = null; + private long maxTimeout = 5000L; // 5 seconds - @Override - public void init(ServletConfig config) throws ServletException { - // Initialize - datastoreService = DatastoreOptions.getDefaultInstance().getService(); + public void setTimeoutMilliSeconds(long timeout) { + maxTimeout = timeout; } @Override @@ -50,68 +47,63 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) return; } - Map> responseBody = new Gson() + Map> requestBody = new Gson() .fromJson(jsonReader, Map.class); - final String data = responseBody.get("message").get("data"); + final String requestData = requestBody.get("message").get("data"); // Ugly... - byte[] interm = Base64.getDecoder().decode(data); - String payload = new String(interm, StandardCharsets.UTF_8); + byte[] decodedData = Base64.getDecoder().decode(requestData); + String stringData = new String(decodedData, StandardCharsets.UTF_8); // Save payload to be displayed later - saveMessage(payload); + saveMessage(stringData); } catch (JsonParseException error) { resp.getWriter().print(error.toString()); resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); } } + public void saveMessage(String message) { + try { + // set starting sleepTime + long timeout = 1000; + // Prepare message list if it's empty + while (timeout < maxTimeout) { + if (createMessageList()) { + break; + } + sleep(timeout); + // Exponential backoff + timeout *= 2; + } + // reset starting sleepTime + timeout = 1000; - public void saveMessage(String message) { - new Thread(new Runnable() { - private final long maxSleepTime = 30000; // 30 seconds - - @Override - public void run() { - try { - // set starting sleepTime - long sleepTime = 1000; - // Prepare message list if it's empty - while (sleepTime < maxSleepTime) { - if (createMessageList()) { - break; - } - sleep(sleepTime); - // Exponential backoff - sleepTime *= 2; - } - - // reset starting sleepTime - sleepTime = 1000; - // Attempt to save message - while (sleepTime < maxSleepTime) { - if (trySaveMessage(message)) { - break; - } - sleep(sleepTime); - } - } catch (InterruptedException ie) { - System.err.println(ie); + // Attempt to save message + while (timeout < maxTimeout) { + if (trySaveMessage(message)) { + break; } + sleep(timeout); + timeout *= 2; } - }).start(); + } catch (InterruptedException ie) { + System.err.println(ie); + } } private boolean createMessageList() { // Start a new transaction + Datastore datastoreService = DatastoreOptions.getDefaultInstance() + .getService(); Transaction transaction = datastoreService.newTransaction(); // Create a Gson object to serialize messages LinkedList as a JSON string Gson gson = new Gson(); - // Transaction flag - boolean messagesFound = false; + // Transaction flag (assume it worked) + boolean messagesFound = true; try { // Create a keyfactory for entries of kind pushed_messages @@ -133,7 +125,6 @@ private boolean createMessageList() { } else { transaction.rollback(); } - messagesFound = true; } finally { if (transaction.isActive()) { // we don't have an entry yet transaction failed @@ -147,13 +138,15 @@ private boolean createMessageList() { private boolean trySaveMessage(String message) { // Start a new transaction + Datastore datastoreService = DatastoreOptions.getDefaultInstance() + .getService(); Transaction transaction = datastoreService.newTransaction(); // Create a Gson object to parse and serialize an LinkedList Gson gson = new Gson(); - // Transaction flag - boolean messagesSaved = false; + // Transaction flag (assume it worked) + boolean messagesSaved = true; try { // Lookup pushed_messages @@ -164,8 +157,7 @@ private boolean trySaveMessage(String message) { // Parse JSON into an LinkedList LinkedList messages = gson.fromJson(entity.getString("messages"), - new TypeToken>() { - }.getType()); + new TypeToken>(){}.getType()); // Add new message and save updated entry messages.add(message); @@ -174,7 +166,6 @@ private boolean trySaveMessage(String message) { .build(); transaction.update(entity); transaction.commit(); - messagesSaved = true; } finally { if (transaction.isActive()) { transaction.rollback(); From c60b4d94158ed78b53aa892bf5d5c129e13963b6 Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Wed, 30 Nov 2016 09:23:31 -0800 Subject: [PATCH 08/24] Removed underscore from test method name --- .../java/com/example/appengine/pubsub/PubSubHomeTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java index a19bcfcdad6..dde73ad944c 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java @@ -65,7 +65,7 @@ public void doGet_OneMessageIT() throws Exception { // Add messages to local Datastore addMessages(messageList); - // Insert messageList.. + // Do GET servletUnderTest.doGet(mockRequest, mockResponse); // We expect our hello world response. @@ -78,7 +78,7 @@ public void doGet_OneMessageIT() throws Exception { } @Test - public void doGet_MultipleMessagesIT() throws Exception { + public void doGetMultipleMessagesIT() throws Exception { LinkedList messageList = new LinkedList<>(); messageList.add("Hello, World!"); messageList.add("Hello, World!"); From 0bd81968ec7ebb810f885a31aa6d4d362528a42f Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Wed, 30 Nov 2016 09:24:32 -0800 Subject: [PATCH 09/24] Added tests for PubSubPlubish and PubSubPush --- .../appengine/pubsub/PubSubPublishTest.java | 88 ++++++++++++ .../appengine/pubsub/PubSubPushTest.java | 131 ++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java create mode 100644 flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java new file mode 100644 index 00000000000..30b6950bcb8 --- /dev/null +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java @@ -0,0 +1,88 @@ +package com.example.appengine.pubsub; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.cloud.pubsub.PubSub; +import com.google.cloud.pubsub.PubSubOptions; +import com.google.cloud.pubsub.ReceivedMessage; +import com.google.cloud.pubsub.SubscriptionInfo; +import com.google.cloud.pubsub.Topic; +import com.google.cloud.pubsub.TopicInfo; +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.Iterator; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class PubSubPublishTest { + private static final String FAKE_URL = "fakeurl.google"; + private static final String topicName = System.getenv("PUBSUB_TOPIC"); + private static final String subscriptionName = System.getenv( + "PUBSUB_SUBSCRIPTION"); + @Mock private HttpServletRequest mockRequest; + @Mock private HttpServletResponse mockResponse; + PubSub pubsub; + private PubSubPublish servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + // Set up some fake HTTP requests + when(mockRequest.getRequestURI()).thenReturn(FAKE_URL); + + // Create an instance of the PubSubHome servlet + servletUnderTest = new PubSubPublish(); + + // Create a Topic with pull subscription + pubsub = PubSubOptions.getDefaultInstance().getService(); + TopicInfo topicInfo = TopicInfo.newBuilder(topicName).build(); + Topic topic = pubsub.create(topicInfo); + SubscriptionInfo subscriptionInfo = SubscriptionInfo.newBuilder( + topicName, subscriptionName).build(); + pubsub.create(subscriptionInfo); + } + + @After + public void tearDown() throws Exception { + // Delete Topic + pubsub.deleteSubscription(subscriptionName); + pubsub.deleteTopic(topicName); + } + + @Test + public void doPostIT() throws Exception { + // Dummy payload + final String dummyPayload = "MessageToPost"; + + // Set payload data for request + when(mockRequest.getParameter("payload")).thenReturn(dummyPayload); + + // Do POST + servletUnderTest.doPost(mockRequest, mockResponse); + + // Pull using subscription to verify publish + SubscriptionInfo subscrptionInfo = SubscriptionInfo.of(topicName, + subscriptionName); + Iterator messages = pubsub.pull(subscriptionName, 1); + + // Check message payload is dummyPayload + if (messages.hasNext()) { + ReceivedMessage message = messages.next(); + assertThat(message.getPayloadAsString()) + .named("Publish Message") + .contains(dummyPayload); + message.ack(); + } + } +} \ No newline at end of file diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java new file mode 100644 index 00000000000..2298c2e6651 --- /dev/null +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java @@ -0,0 +1,131 @@ +package com.example.appengine.pubsub; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + +import com.google.cloud.datastore.Datastore; +import com.google.cloud.datastore.DatastoreOptions; +import com.google.cloud.datastore.Entity; +import com.google.cloud.datastore.Key; +import com.google.cloud.datastore.Query; +import com.google.gson.Gson; +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.BufferedReader; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Base64; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class PubSubPushTest { + private static final String FAKE_URL = "fakeurl.google"; + private static final String KIND = "pushed_messages"; + private static final String KEY = "message_list"; + private static final String FIELD = "messages"; + @Mock private HttpServletRequest mockRequest; + @Mock private HttpServletResponse mockResponse; + private StringWriter responseWriter; + private PubSubPush servletUnderTest; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + // Set up some fake HTTP requests + when(mockRequest.getRequestURI()).thenReturn(FAKE_URL); + + // Add token + String token = System.getenv("PUBSUB_VERIFICATION_TOKEN"); + when(mockRequest.getParameter("token")).thenReturn(token); + + // Set up a fake HTTP response + responseWriter = new StringWriter(); + + // Create an instance of the PubSubHome servlet + servletUnderTest = new PubSubPush(); + servletUnderTest.setTimeoutMilliSeconds(30000); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void doPostSingleMessageIT() throws Exception { + // Clear all messages + clearMessageList(); + + // Mock reader for request + String message = "Hello, World!"; + BufferedReader mockReader = generatePostMessage(message); + when(mockRequest.getReader()).thenReturn(mockReader); + + // Expected output + final LinkedList expectedMessageList = new LinkedList<>(); + expectedMessageList.push(message); + + // Do POST + servletUnderTest.doPost(mockRequest, mockResponse); + + // Test that Message exists in Datastore + LinkedList messages = getMessages(); + assertThat(messages).isEqualTo(expectedMessageList); + + // Clean up + clearMessageList(); + } + + private BufferedReader generatePostMessage(String message) { + String encodedPayload = Base64.getEncoder().encodeToString( + message.getBytes()); + Map> messageObject = new HashMap<>(); + Map dataObject = new HashMap<>(); + messageObject.put("message", dataObject); + dataObject.put("data", encodedPayload); + + Gson gson = new Gson(); + StringReader reader = new StringReader(gson.toJson(messageObject, + messageObject.getClass())); + + return (new BufferedReader(reader)); + } + + private void clearMessageList() { + Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); + + // Clear all message + Key pushedMessages = datastore.newKeyFactory().setKind(KIND).newKey(KEY); + datastore.delete(pushedMessages); + } + + private LinkedList getMessages() { + Gson gson = new Gson(); + Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); + Query query = Query.newEntityQueryBuilder().setKind(KIND) + .setLimit(1).build(); + + Iterator entities = datastore.run(query); + LinkedList messages = new LinkedList<>(); + if (entities.hasNext()) { + Entity entity = entities.next(); + messages = gson.fromJson(entity.getString("messages"), + messages.getClass()); + } + + return messages; + } +} \ No newline at end of file From 396263225159d04af62c955f13ccfbf9a3eb7099 Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Wed, 30 Nov 2016 10:32:44 -0800 Subject: [PATCH 10/24] Refactored string literals to a named variable --- .../example/appengine/pubsub/PubSubHome.java | 12 +++++++-- .../example/appengine/pubsub/PubSubPush.java | 26 ++++++++++++------- .../appengine/pubsub/PubSubHomeTest.java | 19 +++++++++----- .../appengine/pubsub/PubSubPublishTest.java | 1 + .../appengine/pubsub/PubSubPushTest.java | 18 ++++++++----- 5 files changed, 53 insertions(+), 23 deletions(-) diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java index 707b1de1ed3..68a65faa454 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java +++ b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java @@ -18,6 +18,14 @@ @WebServlet(name = "main", value = "/") public class PubSubHome extends HttpServlet { + private static final String ENTRY_FIELD = "messages"; + + private String entryKind = "pushed_message"; + + public void setEntryKind(String kind) { + entryKind = kind; + } + @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { @@ -57,7 +65,7 @@ private LinkedList getMessages() { // Get Message saved in Datastore Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); Query query = Query.newEntityQueryBuilder() - .setKind("pushed_messages") + .setKind(entryKind) .setLimit(1) .build(); QueryResults results = datastore.run(query); @@ -67,7 +75,7 @@ private LinkedList getMessages() { Gson gson = new Gson(); if (results.hasNext()) { Entity entity = results.next(); - messageList = gson.fromJson(entity.getString("messages"), + messageList = gson.fromJson(entity.getString(ENTRY_FIELD), messageList.getClass()); } diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java index 5117350e165..f2fbaac71e1 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java +++ b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java @@ -26,12 +26,20 @@ @WebServlet(name = "PubSubPush", value = "/pubsub/push") public class PubSubPush extends HttpServlet { + private static final String KEY = "message_list"; + private static final String FIELD = "messages"; + private long maxTimeout = 5000L; // 5 seconds + private String entryKind = "pushed_messages"; public void setTimeoutMilliSeconds(long timeout) { maxTimeout = timeout; } + public void setEntryKind(String kind) { + entryKind = kind; + } + @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { @@ -106,19 +114,19 @@ private boolean createMessageList() { boolean messagesFound = true; try { - // Create a keyfactory for entries of kind pushed_messages + // Create a keyfactory for entries of KIND KeyFactory keyFactory = datastoreService.newKeyFactory() - .setKind("pushed_messages"); + .setKind(entryKind); - // Lookup message_list - Key key = keyFactory.newKey("message_list"); + // Lookup KEY + Key key = keyFactory.newKey(KEY); Entity entity = transaction.get(key); // Entity doesn't exist so let's create it! if (entity == null) { LinkedList messages = new LinkedList<>(); entity = Entity.newBuilder(key) - .set("messages", gson.toJson(messages)) + .set(FIELD, gson.toJson(messages)) .build(); transaction.put(entity); transaction.commit(); @@ -151,18 +159,18 @@ private boolean trySaveMessage(String message) { try { // Lookup pushed_messages KeyFactory keyFactory = datastoreService.newKeyFactory() - .setKind("pushed_messages"); - Key key = keyFactory.newKey("message_list"); + .setKind(entryKind); + Key key = keyFactory.newKey(KEY); Entity entity = transaction.get(key); // Parse JSON into an LinkedList - LinkedList messages = gson.fromJson(entity.getString("messages"), + LinkedList messages = gson.fromJson(entity.getString(FIELD), new TypeToken>(){}.getType()); // Add new message and save updated entry messages.add(message); entity = Entity.newBuilder(entity) - .set("messages", gson.toJson(messages)) + .set(FIELD, gson.toJson(messages)) .build(); transaction.update(entity); transaction.commit(); diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java index dde73ad944c..4fcc30838bc 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java @@ -27,16 +27,20 @@ @SuppressWarnings("checkstyle:abbreviationaswordinname") public class PubSubHomeTest { private static final String FAKE_URL = "fakeurl.google"; - private static final String KIND = "pushed_messages"; - private static final String KEY = "message_list"; - private static final String FIELD = "messages"; + private static final String ENTRY_KEY = "message_list"; + private static final String ENTRY_FIELD = "messages"; @Mock private HttpServletRequest mockRequest; @Mock private HttpServletResponse mockResponse; private StringWriter responseWriter; private PubSubHome servletUnderTest; + private String entryKind; @Before public void setUp() throws Exception { + // Get environment variables + entryKind = System.getenv("DATASTORE_TEST_ENTRY_KIND"); + + // Initialize Mockito MockitoAnnotations.initMocks(this); // Set up some fake HTTP requests @@ -48,6 +52,7 @@ public void setUp() throws Exception { // Create an instance of the PubSubHome servlet servletUnderTest = new PubSubHome(); + servletUnderTest.setEntryKind(entryKind); } @After @@ -106,18 +111,20 @@ private void clearMessageList() { .getService(); // Clear all message - Key pushedMessages = datastore.newKeyFactory().setKind(KIND).newKey(KEY); + Key pushedMessages = datastore.newKeyFactory().setKind(entryKind).newKey( + ENTRY_KEY); datastore.delete(pushedMessages); } private void addMessages(LinkedList messageList) { Gson gson = new Gson(); Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); - Key messages = datastore.newKeyFactory().setKind(KIND).newKey(KEY); + Key messages = datastore.newKeyFactory().setKind(entryKind).newKey( + ENTRY_KEY); // Create an entry with a list of messages Entity entity = Entity.newBuilder(messages) - .set(FIELD, gson.toJson(messageList)) + .set(ENTRY_FIELD, gson.toJson(messageList)) .build(); datastore.put(entity); diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java index 30b6950bcb8..e0973428a26 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java @@ -36,6 +36,7 @@ public class PubSubPublishTest { @Before public void setUp() throws Exception { + // Initialize Mockito MockitoAnnotations.initMocks(this); // Set up some fake HTTP requests diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java index 2298c2e6651..f48488ca6df 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java @@ -33,16 +33,20 @@ @SuppressWarnings("checkstyle:abbreviationaswordinname") public class PubSubPushTest { private static final String FAKE_URL = "fakeurl.google"; - private static final String KIND = "pushed_messages"; - private static final String KEY = "message_list"; - private static final String FIELD = "messages"; + private static final String ENTRY_KEY = "message_list"; + private static final String ENTRY_FIELD = "messages"; @Mock private HttpServletRequest mockRequest; @Mock private HttpServletResponse mockResponse; private StringWriter responseWriter; private PubSubPush servletUnderTest; + private String entryKind; @Before public void setUp() throws Exception { + // Get environment variables + entryKind = System.getenv("DATASTORE_TEST_ENTRY_KIND"); + + // Initialize Mockito MockitoAnnotations.initMocks(this); // Set up some fake HTTP requests @@ -58,6 +62,7 @@ public void setUp() throws Exception { // Create an instance of the PubSubHome servlet servletUnderTest = new PubSubPush(); servletUnderTest.setTimeoutMilliSeconds(30000); + servletUnderTest.setEntryKind(entryKind); } @After @@ -108,21 +113,22 @@ private void clearMessageList() { Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); // Clear all message - Key pushedMessages = datastore.newKeyFactory().setKind(KIND).newKey(KEY); + Key pushedMessages = datastore.newKeyFactory().setKind(entryKind) + .newKey(ENTRY_KEY); datastore.delete(pushedMessages); } private LinkedList getMessages() { Gson gson = new Gson(); Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); - Query query = Query.newEntityQueryBuilder().setKind(KIND) + Query query = Query.newEntityQueryBuilder().setKind(entryKind) .setLimit(1).build(); Iterator entities = datastore.run(query); LinkedList messages = new LinkedList<>(); if (entities.hasNext()) { Entity entity = entities.next(); - messages = gson.fromJson(entity.getString("messages"), + messages = gson.fromJson(entity.getString(ENTRY_FIELD), messages.getClass()); } From 2f5771d52a2c59826fa715a7beadc8f5b582ead4 Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Wed, 30 Nov 2016 10:33:29 -0800 Subject: [PATCH 11/24] Added README.md with sample message to test PubSub push subscription --- flexible/pubsub/README.md | 72 +++++++++++++++++++++++++++++ flexible/pubsub/sample_message.json | 5 ++ 2 files changed, 77 insertions(+) create mode 100644 flexible/pubsub/README.md create mode 100644 flexible/pubsub/sample_message.json diff --git a/flexible/pubsub/README.md b/flexible/pubsub/README.md new file mode 100644 index 00000000000..e069f4c1e1a --- /dev/null +++ b/flexible/pubsub/README.md @@ -0,0 +1,72 @@ +# App Engine Flexible Environment - Pub/Sub Sample + +## Clone the sample app + +Copy the sample apps to your local machine, and cd to the pubsub directory: + +``` +git clone https://github.com/GoogleCloudPlatform/java-docs-samples +cd java-docs-samples/flexible/pubsub +``` + +## Create a topic and subscription + +Create a topic and subscription, which includes specifying the +endpoint to which the Pub/Sub server should send requests: + +``` +gcloud beta pubsub topics create +gcloud beta pubsub subscriptions create \ + --topic \ + --push-endpoint \ + https://.appspot.com/pubsub/push?token= \ + --ack-deadline 30 +``` + +## Run + +Make sure `gcloud` is installed and authenticated. Set the following +environment variables and run using shown Maven command. You can then +direct your browser to `http://localhost:8080/` + +``` +export PUBSUB_VERIFICATION_TOKEN= +export PUBSUB_TOPIC= +export GOOGLE_CLOUD_PROJECT= + +mvn jetty:run +``` + + +### Send fake subscription push messages with: + +``` +curl -i --data @sample_message.json +"localhost:8080/pubsub/push?token=" +``` + +## Deploy + +Put topic and token in `app.yaml`, then: + +``` +mvn appengine:deploy +``` + +## Test +Tests use a live service and will create necessary topic and +subscription for tests. + +Set the following environment variables before running tests. +``` +export PUBSUB_VERIFICATION_TOKEN= +export PUBSUB_TOPIC= +export PUBSUB_SUBSCRIPTION= +export DATASTORE_TEST_ENTRY_KIND= +export GOOGLE_CLOUD_PROJECT= +``` + +Run tests in terminal using Maven +``` +mvn verify +``` diff --git a/flexible/pubsub/sample_message.json b/flexible/pubsub/sample_message.json new file mode 100644 index 00000000000..8fe62d23fb9 --- /dev/null +++ b/flexible/pubsub/sample_message.json @@ -0,0 +1,5 @@ +{ + "message": { + "data": "SGVsbG8sIFdvcmxkIQ==" + } +} From 411289d798fd894d784b0a885064c83570b9f79e Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Wed, 30 Nov 2016 13:06:57 -0800 Subject: [PATCH 12/24] Refactored variable instantiations and cleaned up pom.xml --- flexible/pubsub/pom.xml | 10 ++-------- .../example/appengine/pubsub/PubSubHome.java | 9 +++++---- .../example/appengine/pubsub/PubSubPush.java | 5 +++-- .../appengine/pubsub/PubSubHomeTest.java | 14 +++++--------- .../appengine/pubsub/PubSubPublishTest.java | 10 +++++----- .../appengine/pubsub/PubSubPushTest.java | 17 +++++++---------- 6 files changed, 27 insertions(+), 38 deletions(-) diff --git a/flexible/pubsub/pom.xml b/flexible/pubsub/pom.xml index 46fb222c90b..13f5b4e5ca1 100644 --- a/flexible/pubsub/pom.xml +++ b/flexible/pubsub/pom.xml @@ -28,14 +28,11 @@ - + 1.0.0 1.8 1.8 - - 1.0.0 - 9.3.8.v20160314 - false + 9.3.8.v20160314 @@ -112,10 +109,7 @@ com.google.cloud.tools appengine-maven-plugin ${appengine.maven.plugin} - - - org.eclipse.jetty jetty-maven-plugin diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java index 68a65faa454..f255816cec1 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java +++ b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.LinkedList; +import java.util.List; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; @@ -41,7 +42,7 @@ public void doGet(HttpServletRequest req, HttpServletResponse resp) + ""); // Get Messages - LinkedList messageList = getMessages(); + List messageList = getMessages(); // Display received messages out.println("Received Messages:
"); @@ -61,7 +62,7 @@ public void doGet(HttpServletRequest req, HttpServletResponse resp) + ""); } - private LinkedList getMessages() { + private List getMessages() { // Get Message saved in Datastore Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); Query query = Query.newEntityQueryBuilder() @@ -70,8 +71,8 @@ private LinkedList getMessages() { .build(); QueryResults results = datastore.run(query); - // Convert stored JSON into LinkedList - LinkedList messageList = new LinkedList<>(); + // Convert stored JSON into List + List messageList = new LinkedList<>(); Gson gson = new Gson(); if (results.hasNext()) { Entity entity = results.next(); diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java index f2fbaac71e1..5ec3ec41511 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java +++ b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java @@ -17,6 +17,7 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.LinkedList; +import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; @@ -124,7 +125,7 @@ private boolean createMessageList() { // Entity doesn't exist so let's create it! if (entity == null) { - LinkedList messages = new LinkedList<>(); + List messages = new LinkedList<>(); entity = Entity.newBuilder(key) .set(FIELD, gson.toJson(messages)) .build(); @@ -164,7 +165,7 @@ private boolean trySaveMessage(String message) { Entity entity = transaction.get(key); // Parse JSON into an LinkedList - LinkedList messages = gson.fromJson(entity.getString(FIELD), + List messages = gson.fromJson(entity.getString(FIELD), new TypeToken>(){}.getType()); // Add new message and save updated entry diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java index 4fcc30838bc..4e5b68993e2 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java @@ -8,7 +8,6 @@ import com.google.cloud.datastore.Entity; import com.google.cloud.datastore.Key; import com.google.gson.Gson; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -19,6 +18,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.util.LinkedList; +import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -55,13 +55,9 @@ public void setUp() throws Exception { servletUnderTest.setEntryKind(entryKind); } - @After - public void tearDown() throws Exception { - } - @Test public void doGet_OneMessageIT() throws Exception { - LinkedList messageList = new LinkedList<>(); + List messageList = new LinkedList<>(); messageList.add("Hello, World!"); // clear Message list @@ -84,7 +80,7 @@ public void doGet_OneMessageIT() throws Exception { @Test public void doGetMultipleMessagesIT() throws Exception { - LinkedList messageList = new LinkedList<>(); + List messageList = new LinkedList<>(); messageList.add("Hello, World!"); messageList.add("Hello, World!"); @@ -116,7 +112,7 @@ private void clearMessageList() { datastore.delete(pushedMessages); } - private void addMessages(LinkedList messageList) { + private void addMessages(List messageList) { Gson gson = new Gson(); Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); Key messages = datastore.newKeyFactory().setKind(entryKind).newKey( @@ -130,7 +126,7 @@ private void addMessages(LinkedList messageList) { datastore.put(entity); } - private String generateHtmlWithMessageList(LinkedList messageList) { + private String generateHtmlWithMessageList(List messageList) { String html = "\n" + "\n" diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java index e0973428a26..b5cf0e6113a 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java @@ -7,8 +7,8 @@ import com.google.cloud.pubsub.PubSubOptions; import com.google.cloud.pubsub.ReceivedMessage; import com.google.cloud.pubsub.SubscriptionInfo; -import com.google.cloud.pubsub.Topic; import com.google.cloud.pubsub.TopicInfo; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -48,7 +48,9 @@ public void setUp() throws Exception { // Create a Topic with pull subscription pubsub = PubSubOptions.getDefaultInstance().getService(); TopicInfo topicInfo = TopicInfo.newBuilder(topicName).build(); - Topic topic = pubsub.create(topicInfo); + pubsub.create(topicInfo); + + // Pull subscription SubscriptionInfo subscriptionInfo = SubscriptionInfo.newBuilder( topicName, subscriptionName).build(); pubsub.create(subscriptionInfo); @@ -56,7 +58,7 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { - // Delete Topic + // Delete Topic and Subscription pubsub.deleteSubscription(subscriptionName); pubsub.deleteTopic(topicName); } @@ -73,8 +75,6 @@ public void doPostIT() throws Exception { servletUnderTest.doPost(mockRequest, mockResponse); // Pull using subscription to verify publish - SubscriptionInfo subscrptionInfo = SubscriptionInfo.of(topicName, - subscriptionName); Iterator messages = pubsub.pull(subscriptionName, 1); // Check message payload is dummyPayload diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java index f48488ca6df..d193ef42213 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java +++ b/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java @@ -9,7 +9,6 @@ import com.google.cloud.datastore.Key; import com.google.cloud.datastore.Query; import com.google.gson.Gson; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -24,8 +23,10 @@ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.Map; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -65,10 +66,6 @@ public void setUp() throws Exception { servletUnderTest.setEntryKind(entryKind); } - @After - public void tearDown() throws Exception { - } - @Test public void doPostSingleMessageIT() throws Exception { // Clear all messages @@ -80,14 +77,14 @@ public void doPostSingleMessageIT() throws Exception { when(mockRequest.getReader()).thenReturn(mockReader); // Expected output - final LinkedList expectedMessageList = new LinkedList<>(); - expectedMessageList.push(message); + final List expectedMessageList = new LinkedList<>(); + expectedMessageList.add(message); // Do POST servletUnderTest.doPost(mockRequest, mockResponse); // Test that Message exists in Datastore - LinkedList messages = getMessages(); + List messages = getMessages(); assertThat(messages).isEqualTo(expectedMessageList); // Clean up @@ -118,14 +115,14 @@ private void clearMessageList() { datastore.delete(pushedMessages); } - private LinkedList getMessages() { + private List getMessages() { Gson gson = new Gson(); Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); Query query = Query.newEntityQueryBuilder().setKind(entryKind) .setLimit(1).build(); Iterator entities = datastore.run(query); - LinkedList messages = new LinkedList<>(); + List messages = new LinkedList<>(); if (entities.hasNext()) { Entity entity = entities.next(); messages = gson.fromJson(entity.getString(ENTRY_FIELD), From 873dbf52529dbd7c70d540e8701e6598c50ccf61 Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Wed, 30 Nov 2016 14:55:20 -0800 Subject: [PATCH 13/24] Refactored trySaveMessage and createMessageList into a single method --- .../example/appengine/pubsub/PubSubPush.java | 87 +++++++------------ 1 file changed, 30 insertions(+), 57 deletions(-) diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java index 5ec3ec41511..6f21a3d9354 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java +++ b/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java @@ -16,9 +16,11 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; + import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; @@ -27,6 +29,7 @@ @WebServlet(name = "PubSubPush", value = "/pubsub/push") public class PubSubPush extends HttpServlet { + private static final int MAX_ELEMENTS = 10; private static final String KEY = "message_list"; private static final String FIELD = "messages"; @@ -57,10 +60,11 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) } Map> requestBody = new Gson() - .fromJson(jsonReader, Map.class); + .fromJson(jsonReader, new TypeToken>>(){}.getType()); final String requestData = requestBody.get("message").get("data"); - // Ugly... + // Decode data into String byte[] decodedData = Base64.getDecoder().decode(requestData); String stringData = new String(decodedData, StandardCharsets.UTF_8); @@ -76,18 +80,6 @@ public void saveMessage(String message) { try { // set starting sleepTime long timeout = 1000; - // Prepare message list if it's empty - while (timeout < maxTimeout) { - if (createMessageList()) { - break; - } - sleep(timeout); - // Exponential backoff - timeout *= 2; - } - - // reset starting sleepTime - timeout = 1000; // Attempt to save message while (timeout < maxTimeout) { @@ -102,7 +94,7 @@ public void saveMessage(String message) { } } - private boolean createMessageList() { + private boolean trySaveMessage(String message) { // Start a new transaction Datastore datastoreService = DatastoreOptions.getDefaultInstance() .getService(); @@ -112,7 +104,7 @@ private boolean createMessageList() { Gson gson = new Gson(); // Transaction flag (assume it worked) - boolean messagesFound = true; + boolean messagesSaved = true; try { // Create a keyfactory for entries of KIND @@ -126,62 +118,43 @@ private boolean createMessageList() { // Entity doesn't exist so let's create it! if (entity == null) { List messages = new LinkedList<>(); + messages.add(message); + + // Create update entity entity = Entity.newBuilder(key) .set(FIELD, gson.toJson(messages)) .build(); - transaction.put(entity); - transaction.commit(); - } else { - transaction.rollback(); - } - } finally { - if (transaction.isActive()) { - // we don't have an entry yet transaction failed - transaction.rollback(); - messagesFound = false; - } - } - // we have an entry to work with - return messagesFound; - } - - private boolean trySaveMessage(String message) { - // Start a new transaction - Datastore datastoreService = DatastoreOptions.getDefaultInstance() - .getService(); - Transaction transaction = datastoreService.newTransaction(); - // Create a Gson object to parse and serialize an LinkedList - Gson gson = new Gson(); + } else { // It does exist + // Parse JSON into an LinkedList + List messages = gson.fromJson(entity.getString(FIELD), + new TypeToken>(){}.getType()); - // Transaction flag (assume it worked) - boolean messagesSaved = true; + // Add message to head of list + messages.add(0,message); - try { - // Lookup pushed_messages - KeyFactory keyFactory = datastoreService.newKeyFactory() - .setKind(entryKind); - Key key = keyFactory.newKey(KEY); - Entity entity = transaction.get(key); + // End index + int endIndex = Math.min(messages.size(), MAX_ELEMENTS); - // Parse JSON into an LinkedList - List messages = gson.fromJson(entity.getString(FIELD), - new TypeToken>(){}.getType()); + // subList out at most MAX_ELEMENTS messages + messages = messages.subList(0, endIndex); - // Add new message and save updated entry - messages.add(message); - entity = Entity.newBuilder(entity) - .set(FIELD, gson.toJson(messages)) - .build(); - transaction.update(entity); + // Update entity + entity = Entity.newBuilder(entity) + .set(FIELD, gson.toJson(messages)) + .build(); + } + // Save and commit update + transaction.put(entity); transaction.commit(); } finally { if (transaction.isActive()) { + // we don't have an entry yet transaction failed transaction.rollback(); messagesSaved = false; } } - // It saved the new entry! + // we have an entry to work with return messagesSaved; } } From 88048cfc03b15399a38478f599c9517469b7cae4 Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Wed, 30 Nov 2016 14:58:33 -0800 Subject: [PATCH 14/24] Updated pom project information --- flexible/pubsub/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flexible/pubsub/pom.xml b/flexible/pubsub/pom.xml index 13f5b4e5ca1..674781dac74 100644 --- a/flexible/pubsub/pom.xml +++ b/flexible/pubsub/pom.xml @@ -17,8 +17,8 @@ 4.0.0 war 1.0-SNAPSHOT - com.example.managedvms - managed-vms-pubsub + com.example.appengine + appengine-pubsub doc-samples From a964fc32376dbb8d47ee66cd9c9cdd428d41cb4f Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Fri, 2 Dec 2016 17:54:39 -0800 Subject: [PATCH 15/24] Code clean up and directory change - changed com/example/appengine/pubsub to com/example/flexible/pubsub - added Apache License - removed unnecessary dependencies --- flexible/pubsub/pom.xml | 22 +++++++------------ flexible/pubsub/src/main/appengine/app.yaml | 16 +++++++++++++- .../pubsub/PubSubHome.java | 18 ++++++++++++++- .../pubsub/PubSubPublish.java | 18 ++++++++++++++- .../pubsub/PubSubPush.java | 18 ++++++++++++++- .../pubsub/PubSubHomeTest.java | 18 ++++++++++++++- .../pubsub/PubSubPublishTest.java | 18 ++++++++++++++- .../pubsub/PubSubPushTest.java | 18 ++++++++++++++- 8 files changed, 125 insertions(+), 21 deletions(-) rename flexible/pubsub/src/main/java/com/example/{appengine => flexible}/pubsub/PubSubHome.java (80%) rename flexible/pubsub/src/main/java/com/example/{appengine => flexible}/pubsub/PubSubPublish.java (62%) rename flexible/pubsub/src/main/java/com/example/{appengine => flexible}/pubsub/PubSubPush.java (88%) rename flexible/pubsub/src/test/java/com/example/{appengine => flexible}/pubsub/PubSubHomeTest.java (87%) rename flexible/pubsub/src/test/java/com/example/{appengine => flexible}/pubsub/PubSubPublishTest.java (80%) rename flexible/pubsub/src/test/java/com/example/{appengine => flexible}/pubsub/PubSubPushTest.java (86%) diff --git a/flexible/pubsub/pom.xml b/flexible/pubsub/pom.xml index 674781dac74..ac344463ef5 100644 --- a/flexible/pubsub/pom.xml +++ b/flexible/pubsub/pom.xml @@ -13,12 +13,13 @@ See the License for the specific language governing permissions and limitations under the License. --> + 4.0.0 war 1.0-SNAPSHOT - com.example.appengine - appengine-pubsub + com.example.flexible + flexible-pubsub doc-samples @@ -31,16 +32,11 @@ 1.0.0 1.8 1.8 - false + false 9.3.8.v20160314 - - com.google.appengine - appengine-api-1.0-sdk - 1.9.38 - javax.servlet javax.servlet-api @@ -63,12 +59,6 @@ - - com.google.appengine - appengine-testing - 1.9.38 - test - com.google.appengine appengine-api-stubs @@ -109,6 +99,9 @@ com.google.cloud.tools appengine-maven-plugin ${appengine.maven.plugin} + + +
org.eclipse.jetty @@ -118,3 +111,4 @@
+ \ No newline at end of file diff --git a/flexible/pubsub/src/main/appengine/app.yaml b/flexible/pubsub/src/main/appengine/app.yaml index 1735fe7a5b7..9a9a66a29f3 100644 --- a/flexible/pubsub/src/main/appengine/app.yaml +++ b/flexible/pubsub/src/main/appengine/app.yaml @@ -1,3 +1,17 @@ +# 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 appyaml] runtime: java env: flex @@ -13,4 +27,4 @@ env_variables: # application. It can be any sufficiently random string. PUBSUB_VERIFICATION_TOKEN: # [END env_variables] - +# [END appyaml] diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java similarity index 80% rename from flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java rename to flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java index f255816cec1..bcdd1cac9c7 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubHome.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java @@ -1,4 +1,20 @@ -package com.example.appengine.pubsub; +/** + * Copyright 2015 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.flexible.pubsub; import com.google.cloud.datastore.Datastore; import com.google.cloud.datastore.DatastoreOptions; diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPublish.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java similarity index 62% rename from flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPublish.java rename to flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java index df6ea9c65fa..b6e0b2479b6 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPublish.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java @@ -1,4 +1,20 @@ -package com.example.appengine.pubsub; +/** + * 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.flexible.pubsub; import com.google.cloud.pubsub.Message; import com.google.cloud.pubsub.PubSub; diff --git a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java similarity index 88% rename from flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java rename to flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java index 6f21a3d9354..662391270a5 100644 --- a/flexible/pubsub/src/main/java/com/example/appengine/pubsub/PubSubPush.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java @@ -1,4 +1,20 @@ -package com.example.appengine.pubsub; +/** + * 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.flexible.pubsub; import static java.lang.Thread.sleep; diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java b/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubHomeTest.java similarity index 87% rename from flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java rename to flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubHomeTest.java index 4e5b68993e2..ad13e362275 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubHomeTest.java +++ b/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubHomeTest.java @@ -1,4 +1,20 @@ -package com.example.appengine.pubsub; +/** + * 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.flexible.pubsub; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java b/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPublishTest.java similarity index 80% rename from flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java rename to flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPublishTest.java index b5cf0e6113a..a80f0637699 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPublishTest.java +++ b/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPublishTest.java @@ -1,4 +1,20 @@ -package com.example.appengine.pubsub; +/** + * 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.flexible.pubsub; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; diff --git a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java b/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPushTest.java similarity index 86% rename from flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java rename to flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPushTest.java index d193ef42213..b9296ac4d13 100644 --- a/flexible/pubsub/src/test/java/com/example/appengine/pubsub/PubSubPushTest.java +++ b/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPushTest.java @@ -1,4 +1,20 @@ -package com.example.appengine.pubsub; +/** + * 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.flexible.pubsub; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; From 54c3798fbb479f415bb4b72400ab51998dd6836e Mon Sep 17 00:00:00 2001 From: Frank Natividad Date: Sun, 4 Dec 2016 20:35:59 -0800 Subject: [PATCH 16/24] Changed year from 2015 to 2016. --- .../src/main/java/com/example/flexible/pubsub/PubSubHome.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java index bcdd1cac9c7..1f99ea55b21 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java @@ -1,5 +1,5 @@ /** - * Copyright 2015 Google Inc. All Rights Reserved. + * 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. From c5c972b9b08fe88c980bd1c7e794bbcbc103d929 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Wed, 5 Apr 2017 21:45:44 -0700 Subject: [PATCH 17/24] pubsub flex sample rewrite --- flexible/pubsub/README.md | 46 ++--- flexible/pubsub/pom.xml | 12 +- flexible/pubsub/sample_message.json | 6 +- flexible/pubsub/src/main/appengine/app.yaml | 10 +- .../com/example/flexible/pubsub/Message.java | 54 ++++++ .../flexible/pubsub/MessageRepository.java | 98 ++++++++++ .../example/flexible/pubsub/PubSubHome.java | 101 ---------- .../flexible/pubsub/PubSubPublish.java | 47 ++--- .../example/flexible/pubsub/PubSubPush.java | 176 +++--------------- .../flexible/pubsub/PubSubService.java | 128 +++++++++++++ flexible/pubsub/src/main/webapp/index.jsp | 32 ++++ .../flexible/pubsub/PubSubHomeTest.java | 170 ----------------- .../flexible/pubsub/PubSubPublishTest.java | 105 ----------- .../flexible/pubsub/PubSubPushTest.java | 150 --------------- pom.xml | 1 + 15 files changed, 387 insertions(+), 749 deletions(-) create mode 100644 flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java create mode 100644 flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java delete mode 100644 flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java create mode 100644 flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubService.java create mode 100644 flexible/pubsub/src/main/webapp/index.jsp delete mode 100644 flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubHomeTest.java delete mode 100644 flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPublishTest.java delete mode 100644 flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPushTest.java diff --git a/flexible/pubsub/README.md b/flexible/pubsub/README.md index e069f4c1e1a..614ab817f31 100644 --- a/flexible/pubsub/README.md +++ b/flexible/pubsub/README.md @@ -9,31 +9,33 @@ git clone https://github.com/GoogleCloudPlatform/java-docs-samples cd java-docs-samples/flexible/pubsub ``` -## Create a topic and subscription +## Setup -Create a topic and subscription, which includes specifying the -endpoint to which the Pub/Sub server should send requests: +Make sure `gcloud` is installed and authenticated. +Create a topic ``` gcloud beta pubsub topics create +``` + +(Optional) Create a subscription, which includes specifying the endpoint to which the Pub/Sub server should send requests. +This is automatically done if the subscription by name : `` does not exist. +``` gcloud beta pubsub subscriptions create \ --topic \ --push-endpoint \ - https://.appspot.com/pubsub/push?token= \ + https://.appspot.com/pubsub/push?token= \ --ack-deadline 30 ``` - ## Run -Make sure `gcloud` is installed and authenticated. Set the following -environment variables and run using shown Maven command. You can then +Set the following environment variables and run using shown Maven command. You can then direct your browser to `http://localhost:8080/` ``` -export PUBSUB_VERIFICATION_TOKEN= export PUBSUB_TOPIC= -export GOOGLE_CLOUD_PROJECT= - +export PUBSUB_VERIFICATION_TOKEN= +export PUBSUB_SUBSCRIPTION_ID= mvn jetty:run ``` @@ -41,32 +43,18 @@ mvn jetty:run ### Send fake subscription push messages with: ``` -curl -i --data @sample_message.json +curl -H "Content-Type: application/json" -i --data @sample_message.json "localhost:8080/pubsub/push?token=" ``` ## Deploy -Put topic and token in `app.yaml`, then: +Update the environment variables `PUBSUB_TOPIC`, `PUBSUB_VERIFICATION_TOKEN` and `PUBSUB_SUBSCRIPTION_ID` in `app.yaml`, +then: ``` mvn appengine:deploy ``` -## Test -Tests use a live service and will create necessary topic and -subscription for tests. - -Set the following environment variables before running tests. -``` -export PUBSUB_VERIFICATION_TOKEN= -export PUBSUB_TOPIC= -export PUBSUB_SUBSCRIPTION= -export DATASTORE_TEST_ENTRY_KIND= -export GOOGLE_CLOUD_PROJECT= -``` - -Run tests in terminal using Maven -``` -mvn verify -``` +The home page of this application provides a form to publish messages and also provides a view of the most recent messages +received over the push endpoint and persisted in storage. \ No newline at end of file diff --git a/flexible/pubsub/pom.xml b/flexible/pubsub/pom.xml index ac344463ef5..a128dfce697 100644 --- a/flexible/pubsub/pom.xml +++ b/flexible/pubsub/pom.xml @@ -44,17 +44,17 @@ jar provided - - com.google.cloud - google-cloud-datastore - 0.6.0 - com.google.cloud google-cloud-pubsub - 0.6.0 + 0.11.1-alpha + + + com.google.cloud + google-cloud-datastore + 0.11.1-beta diff --git a/flexible/pubsub/sample_message.json b/flexible/pubsub/sample_message.json index 8fe62d23fb9..1c0e04caa1a 100644 --- a/flexible/pubsub/sample_message.json +++ b/flexible/pubsub/sample_message.json @@ -1,5 +1 @@ -{ - "message": { - "data": "SGVsbG8sIFdvcmxkIQ==" - } -} +{"message":{"data":"dGVzdA==","attributes":{},"messageId":"91010751788941","publishTime":"2017-04-05T23:16:42.302Z"}} diff --git a/flexible/pubsub/src/main/appengine/app.yaml b/flexible/pubsub/src/main/appengine/app.yaml index 9a9a66a29f3..f9dd149e744 100644 --- a/flexible/pubsub/src/main/appengine/app.yaml +++ b/flexible/pubsub/src/main/appengine/app.yaml @@ -1,5 +1,4 @@ -# Copyright 2016 Google Inc. All Rights Reserved. -# +# Copyright 2017 Google Inc. # 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 @@ -22,9 +21,8 @@ handlers: # [START env_variables] env_variables: - PUBSUB_TOPIC: - # This token is used to verify that requests originate from your - # application. It can be any sufficiently random string. - PUBSUB_VERIFICATION_TOKEN: + PUBSUB_TOPIC: test-topic-1 + PUBSUB_VERIFICATION_TOKEN: verification-token-123234 + PUBSUB_SUBSCRIPTION_NAME : subscription-name-1 # [END env_variables] # [END appyaml] diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java new file mode 100644 index 00000000000..a7aaa8de076 --- /dev/null +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java @@ -0,0 +1,54 @@ +/** + * Copyright 2017 Google Inc. + * + *

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.flexible.pubsub; + +/** + * A message captures information from the Pubsub message received over the push endpoint and is + * persisted in storage. + */ +public class Message { + private String id; + private String publishTime; + private String data; + + public Message() {} + + public Message(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getPublishTime() { + return publishTime; + } + + public void setPublishTime(String publishTime) { + this.publishTime = publishTime; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } +} diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java new file mode 100644 index 00000000000..59c72a3c703 --- /dev/null +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java @@ -0,0 +1,98 @@ +/** + * Copyright 2017 Google Inc. + * + *

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.flexible.pubsub; + +import com.google.cloud.datastore.*; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +/** Storage for Message objects using Cloud Datastore. */ +public class MessageRepository { + private static String messagesKind = "messages"; + private KeyFactory keyFactory = getDatastoreInstance().newKeyFactory().setKind(messagesKind); + + /** Create a lazy initialized singleton of MessageRepository. */ + private static class LazyInit { + private static final MessageRepository INSTANCE; + + static { + INSTANCE = new MessageRepository(); + } + } + + public static MessageRepository getInstance() { + return LazyInit.INSTANCE; + } + + /** Save message id, data (decoded from base64), publish time */ + public void save(Message message) { + // Save message to "messages" + Datastore datastore = getDatastoreInstance(); + Key key = datastore.allocateId(keyFactory.newKey()); + + Entity.Builder messageEntityBuilder = Entity.newBuilder(key).set("messageId", message.getId()); + + if (message.getData() != null) { + messageEntityBuilder = messageEntityBuilder.set("data", decode(message.getData())); + } + + if (message.getPublishTime() != null) { + messageEntityBuilder = messageEntityBuilder.set("publishTime", message.getPublishTime()); + } + datastore.put(messageEntityBuilder.build()); + } + + /** + * Retrieve most recent messages. + * + * @param limit number of messages + * @return list of messages + */ + public List retrieve(int limit) { + // Get Message saved in Datastore + Datastore datastore = getDatastoreInstance(); + Query query = + Query.newEntityQueryBuilder() + .setKind(messagesKind) + .setLimit(limit) + .addOrderBy(StructuredQuery.OrderBy.desc("publishTime")) + .build(); + QueryResults results = datastore.run(query); + + List messages = new ArrayList<>(); + while (results.hasNext()) { + Entity entity = results.next(); + Message message = new Message(entity.getString("messageId")); + String data = entity.getString("data"); + if (data != null) { + message.setData(data); + } + String publishTime = entity.getString("publishTime"); + if (publishTime != null) { + message.setPublishTime(publishTime); + } + messages.add(message); + } + return messages; + } + + private String decode(String data) { + return new String(Base64.getDecoder().decode(data)); + } + + private Datastore getDatastoreInstance() { + return DatastoreOptions.getDefaultInstance().getService(); + } +} diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java deleted file mode 100644 index 1f99ea55b21..00000000000 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * 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.flexible.pubsub; - -import com.google.cloud.datastore.Datastore; -import com.google.cloud.datastore.DatastoreOptions; -import com.google.cloud.datastore.Entity; -import com.google.cloud.datastore.Query; -import com.google.cloud.datastore.QueryResults; -import com.google.gson.Gson; - -import java.io.IOException; -import java.io.PrintWriter; -import java.util.LinkedList; -import java.util.List; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -@WebServlet(name = "main", value = "/") -public class PubSubHome extends HttpServlet { - private static final String ENTRY_FIELD = "messages"; - - private String entryKind = "pushed_message"; - - public void setEntryKind(String kind) { - entryKind = kind; - } - - @Override - public void doGet(HttpServletRequest req, HttpServletResponse resp) - throws IOException, ServletException { - // Prepare to present list - resp.setContentType("text/html"); - PrintWriter out = resp.getWriter(); - out.println("\n" - + "\n" - + "\n" - + "Send a PubSub Message\n" - + "\n" - + ""); - - // Get Messages - List messageList = getMessages(); - - // Display received messages - out.println("Received Messages:
"); - for (String message : messageList) { - out.printf("%s
\n", message); - } - - // Add Form to publish a new message - out.println("

\n" - + "\n" - + "\n" - + "\n" - + "
"); - - // Finish HTML Response - out.println("\n" - + ""); - } - - private List getMessages() { - // Get Message saved in Datastore - Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); - Query query = Query.newEntityQueryBuilder() - .setKind(entryKind) - .setLimit(1) - .build(); - QueryResults results = datastore.run(query); - - // Convert stored JSON into List - List messageList = new LinkedList<>(); - Gson gson = new Gson(); - if (results.hasNext()) { - Entity entity = results.next(); - messageList = gson.fromJson(entity.getString(ENTRY_FIELD), - messageList.getClass()); - } - - return messageList; - } -} diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java index b6e0b2479b6..c33e7b2f527 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java @@ -1,50 +1,39 @@ /** - * Copyright 2016 Google Inc. All Rights Reserved. + * Copyright 2017 Google Inc. * - * 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.flexible.pubsub; -import com.google.cloud.pubsub.Message; -import com.google.cloud.pubsub.PubSub; -import com.google.cloud.pubsub.PubSubOptions; -import com.google.cloud.pubsub.Topic; - import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.http.HttpStatus; -@WebServlet(name = "PubSubPublish", value = "/publish") +@WebServlet(name = "Publish with PubSub", value = "/pubsub/publish") public class PubSubPublish extends HttpServlet { @Override - public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, - ServletException { - // Load environment variables - final String topicName = System.getenv("PUBSUB_TOPIC"); + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { final String payload = req.getParameter("payload"); - // Create a PubSub Service and get Topic - PubSub pubsub = PubSubOptions.getDefaultInstance().getService(); - Topic topic = pubsub.getTopic(topicName); - - // Publish Message - topic.publish(Message.of(payload)); - - // redirect "/" - resp.sendRedirect("/"); + PubSubService pubSubService = PubSubService.getInstance(); + try { + pubSubService.publish(payload); + resp.sendRedirect("/"); + } catch (Exception e) { + resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + } } } diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java index 662391270a5..469b33e8d08 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java @@ -1,177 +1,57 @@ /** - * Copyright 2016 Google Inc. All Rights Reserved. + * Copyright 2017 Google Inc. * - * 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.flexible.pubsub; -import static java.lang.Thread.sleep; - -import com.google.cloud.datastore.Datastore; -import com.google.cloud.datastore.DatastoreOptions; -import com.google.cloud.datastore.Entity; -import com.google.cloud.datastore.Key; -import com.google.cloud.datastore.KeyFactory; -import com.google.cloud.datastore.Transaction; -import com.google.gson.Gson; -import com.google.gson.JsonParseException; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; - +import com.google.gson.*; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - +import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -@WebServlet(name = "PubSubPush", value = "/pubsub/push") +@WebServlet(name = "Push with PubSub", value = "/pubsub/push") public class PubSubPush extends HttpServlet { - private static final int MAX_ELEMENTS = 10; - private static final String KEY = "message_list"; - private static final String FIELD = "messages"; - private long maxTimeout = 5000L; // 5 seconds - private String entryKind = "pushed_messages"; - - public void setTimeoutMilliSeconds(long timeout) { - maxTimeout = timeout; - } - - public void setEntryKind(String kind) { - entryKind = kind; - } + private final MessageRepository messageRepository = MessageRepository.getInstance(); + private final Gson gson = new Gson(); + private final JsonParser jsonParser = new JsonParser(); @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - final String apiToken = System.getenv("PUBSUB_VERIFICATION_TOKEN"); - - try { - // message = JSON.parse request.body.read - JsonReader jsonReader = new JsonReader(req.getReader()); - - // Token doesn't match apiToken - if (req.getParameter("token").compareTo(apiToken) != 0) { - resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); - return; - } - - Map> requestBody = new Gson() - .fromJson(jsonReader, new TypeToken>>(){}.getType()); - final String requestData = requestBody.get("message").get("data"); - - // Decode data into String - byte[] decodedData = Base64.getDecoder().decode(requestData); - String stringData = new String(decodedData, StandardCharsets.UTF_8); - - // Save payload to be displayed later - saveMessage(stringData); - } catch (JsonParseException error) { - resp.getWriter().print(error.toString()); + String pubsubVerificationToken = System.getenv("PUBSUB_VERIFICATION_TOKEN"); + // Do not process message if request token does not match pubsubVerificationToken + if (req.getParameter("token").compareTo(pubsubVerificationToken) != 0) { resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + return; } - } - - public void saveMessage(String message) { + Message message = getMessage(req); try { - // set starting sleepTime - long timeout = 1000; - - // Attempt to save message - while (timeout < maxTimeout) { - if (trySaveMessage(message)) { - break; - } - sleep(timeout); - timeout *= 2; - } - } catch (InterruptedException ie) { - System.err.println(ie); + messageRepository.save(message); + // 200, 201, 204, 102 status codes are interpreted as success by the Pub/Sub system + resp.setStatus(200); + } catch (Exception e) { + resp.setStatus(500); } } - private boolean trySaveMessage(String message) { - // Start a new transaction - Datastore datastoreService = DatastoreOptions.getDefaultInstance() - .getService(); - Transaction transaction = datastoreService.newTransaction(); - - // Create a Gson object to serialize messages LinkedList as a JSON string - Gson gson = new Gson(); - - // Transaction flag (assume it worked) - boolean messagesSaved = true; - - try { - // Create a keyfactory for entries of KIND - KeyFactory keyFactory = datastoreService.newKeyFactory() - .setKind(entryKind); - - // Lookup KEY - Key key = keyFactory.newKey(KEY); - Entity entity = transaction.get(key); - - // Entity doesn't exist so let's create it! - if (entity == null) { - List messages = new LinkedList<>(); - messages.add(message); - - // Create update entity - entity = Entity.newBuilder(key) - .set(FIELD, gson.toJson(messages)) - .build(); - - } else { // It does exist - // Parse JSON into an LinkedList - List messages = gson.fromJson(entity.getString(FIELD), - new TypeToken>(){}.getType()); - - // Add message to head of list - messages.add(0,message); - - // End index - int endIndex = Math.min(messages.size(), MAX_ELEMENTS); - - // subList out at most MAX_ELEMENTS messages - messages = messages.subList(0, endIndex); - - // Update entity - entity = Entity.newBuilder(entity) - .set(FIELD, gson.toJson(messages)) - .build(); - } - // Save and commit update - transaction.put(entity); - transaction.commit(); - } finally { - if (transaction.isActive()) { - // we don't have an entry yet transaction failed - transaction.rollback(); - messagesSaved = false; - } - } - // we have an entry to work with - return messagesSaved; + private Message getMessage(HttpServletRequest request) throws IOException { + String requestBody = request.getReader().lines().collect(Collectors.joining("\n")); + JsonElement jsonRoot = jsonParser.parse(requestBody); + String message = jsonRoot.getAsJsonObject().get("message").toString(); + return gson.fromJson(message, Message.class); } } - diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubService.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubService.java new file mode 100644 index 00000000000..a0362bf68a9 --- /dev/null +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubService.java @@ -0,0 +1,128 @@ +/** + * Copyright 2017 Google Inc. + * + *

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.flexible.pubsub; + +import com.google.api.gax.grpc.ApiException; +import com.google.cloud.ServiceOptions; +import com.google.cloud.pubsub.spi.v1.Publisher; +import com.google.cloud.pubsub.spi.v1.SubscriptionAdminClient; +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.*; +import io.grpc.Status; +import java.util.logging.Logger; + +public class PubSubService { + + private Publisher publisher; + private Logger logger = Logger.getLogger(this.getClass().getName()); + + /** Create a lazy initialized singleton of PubSubService */ + private static class LazyInit { + private static final PubSubService INSTANCE; + + static { + System.out.println("instance"); + INSTANCE = new PubSubService(); + } + } + + public static PubSubService getInstance() { + return LazyInit.INSTANCE; + } + + /** + * Construct the push endpoint URL on appspot.com with verification token + * + * @return push endpoint + */ + private static String getPushEndPoint() { + return "https://" + + getProjectId() + + ".appspot.com/pubsub/push?token=" + + getPushVerificationToken(); + } + + private static String getPushVerificationToken() { + return System.getenv("PUBSUB_VERIFICATION_TOKEN"); + } + + /** + * Initialize the pubsub service : - create a publisher on a topic and add a subscriber with push + * endpoint for the topic + */ + private PubSubService() { + try { + String topicId = System.getenv("PUBSUB_TOPIC"); + String endpoint = getPushEndPoint(); + String subscriptionId = System.getenv("PUBSUB_SUBSCRIPTION_ID"); + + this.publisher = Publisher.newBuilder(getTopicName(topicId)).build(); + addPushEndPoint(topicId, subscriptionId, endpoint); + } catch (Exception e) { + logger.severe(e.toString()); + } + } + + /** + * Publish a message on configured topic + * + * @param message String + * @throws Exception + */ + public void publish(String message) throws Exception { + PubsubMessage pubsubMessage = + PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8(message)).build(); + publisher.publish(pubsubMessage); + } + + private TopicName getTopicName(String topicId) { + return TopicName.create(getProjectId(), topicId); + } + + private SubscriptionName getSubscriptionName(String subscriptionId) { + return SubscriptionName.create(getProjectId(), subscriptionId); + } + + private static String getProjectId() { + return ServiceOptions.getDefaultProjectId(); + } + + /** + * Creates/modifies subscription with push configuration on topic + * + * @param topicId topic-id + * @param subscriptionId subscription-id + * @param endpoint push endpoint URL + * @throws Exception + */ + private void addPushEndPoint(String topicId, String subscriptionId, String endpoint) + throws Exception { + PushConfig pushConfig = PushConfig.newBuilder().setPushEndpoint(endpoint).build(); + SubscriptionName subscriptionName = getSubscriptionName(subscriptionId); + try (SubscriptionAdminClient subscriberAdminClient = SubscriptionAdminClient.create()) { + try { + subscriberAdminClient.createSubscription( + subscriptionName, getTopicName(topicId), pushConfig, 3); + } catch (ApiException e) { + // modify push config for existing subscription + if (e.getStatusCode().toStatus().equals(Status.ALREADY_EXISTS)) { + subscriberAdminClient.modifyPushConfig(subscriptionName, pushConfig); + return; + } + // otherwise, re-throw + throw e; + } + } + } +} diff --git a/flexible/pubsub/src/main/webapp/index.jsp b/flexible/pubsub/src/main/webapp/index.jsp new file mode 100644 index 00000000000..d8b77c53a97 --- /dev/null +++ b/flexible/pubsub/src/main/webapp/index.jsp @@ -0,0 +1,32 @@ +<%@ page import="com.example.flexible.pubsub.Message" %> +<%@ page import="com.example.flexible.pubsub.MessageRepository" %> +<%@ page import="java.util.List" %> + + +An example of using PubSub on App Engine Flex + +

Publish a message

+
+ + + +
+

Last received messages

+<%! List messages = MessageRepository.getInstance().retrieve(10); %> + + + + + + + <% + for (Message message : messages) {%> + + + + + + <%}%> +
IdMessagePublish time
>>>
+ + \ No newline at end of file diff --git a/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubHomeTest.java b/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubHomeTest.java deleted file mode 100644 index ad13e362275..00000000000 --- a/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubHomeTest.java +++ /dev/null @@ -1,170 +0,0 @@ -/** - * 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.flexible.pubsub; - -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.when; - -import com.google.cloud.datastore.Datastore; -import com.google.cloud.datastore.DatastoreOptions; -import com.google.cloud.datastore.Entity; -import com.google.cloud.datastore.Key; -import com.google.gson.Gson; -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.LinkedList; -import java.util.List; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class PubSubHomeTest { - private static final String FAKE_URL = "fakeurl.google"; - private static final String ENTRY_KEY = "message_list"; - private static final String ENTRY_FIELD = "messages"; - @Mock private HttpServletRequest mockRequest; - @Mock private HttpServletResponse mockResponse; - private StringWriter responseWriter; - private PubSubHome servletUnderTest; - private String entryKind; - - @Before - public void setUp() throws Exception { - // Get environment variables - entryKind = System.getenv("DATASTORE_TEST_ENTRY_KIND"); - - // Initialize Mockito - MockitoAnnotations.initMocks(this); - - // Set up some fake HTTP requests - when(mockRequest.getRequestURI()).thenReturn(FAKE_URL); - - // Set up a fake HTTP response. - responseWriter = new StringWriter(); - when(mockResponse.getWriter()).thenReturn(new PrintWriter(responseWriter)); - - // Create an instance of the PubSubHome servlet - servletUnderTest = new PubSubHome(); - servletUnderTest.setEntryKind(entryKind); - } - - @Test - public void doGet_OneMessageIT() throws Exception { - List messageList = new LinkedList<>(); - messageList.add("Hello, World!"); - - // clear Message list - clearMessageList(); - - // Add messages to local Datastore - addMessages(messageList); - - // Do GET - servletUnderTest.doGet(mockRequest, mockResponse); - - // We expect our hello world response. - assertThat(responseWriter.toString()) - .named("Send a PubSub Message") - .contains(generateHtmlWithMessageList(messageList)); - - // clean up - clearMessageList(); - } - - @Test - public void doGetMultipleMessagesIT() throws Exception { - List messageList = new LinkedList<>(); - messageList.add("Hello, World!"); - messageList.add("Hello, World!"); - - // clear Message list - clearMessageList(); - - // Add messages to local Datastore - addMessages(messageList); - - //Insert messageList - servletUnderTest.doGet(mockRequest, mockResponse); - - // Expect two Hello, World! messages - assertThat(responseWriter.toString()) - .named("Send a PubSub Message") - .contains(generateHtmlWithMessageList(messageList)); - - // clean up - clearMessageList(); - } - - private void clearMessageList() { - Datastore datastore = DatastoreOptions.getDefaultInstance() - .getService(); - - // Clear all message - Key pushedMessages = datastore.newKeyFactory().setKind(entryKind).newKey( - ENTRY_KEY); - datastore.delete(pushedMessages); - } - - private void addMessages(List messageList) { - Gson gson = new Gson(); - Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); - Key messages = datastore.newKeyFactory().setKind(entryKind).newKey( - ENTRY_KEY); - - // Create an entry with a list of messages - Entity entity = Entity.newBuilder(messages) - .set(ENTRY_FIELD, gson.toJson(messageList)) - .build(); - - datastore.put(entity); - } - - private String generateHtmlWithMessageList(List messageList) { - String html = "\n" - + "\n" - + "\n" - + "Send a PubSub Message\n" - + "\n" - + "\n" - + "Received Messages:
\n"; - - for (String message : messageList) { - html += message + "
\n"; - } - - // Add Form to publish a new message - html += "
\n" - + "\n" - + "\n" - + "\n" - + "
\n" - + "\n" - + "\n"; - - return html; - } -} \ No newline at end of file diff --git a/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPublishTest.java b/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPublishTest.java deleted file mode 100644 index a80f0637699..00000000000 --- a/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPublishTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * 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.flexible.pubsub; - -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.when; - -import com.google.cloud.pubsub.PubSub; -import com.google.cloud.pubsub.PubSubOptions; -import com.google.cloud.pubsub.ReceivedMessage; -import com.google.cloud.pubsub.SubscriptionInfo; -import com.google.cloud.pubsub.TopicInfo; - -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.Iterator; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class PubSubPublishTest { - private static final String FAKE_URL = "fakeurl.google"; - private static final String topicName = System.getenv("PUBSUB_TOPIC"); - private static final String subscriptionName = System.getenv( - "PUBSUB_SUBSCRIPTION"); - @Mock private HttpServletRequest mockRequest; - @Mock private HttpServletResponse mockResponse; - PubSub pubsub; - private PubSubPublish servletUnderTest; - - @Before - public void setUp() throws Exception { - // Initialize Mockito - MockitoAnnotations.initMocks(this); - - // Set up some fake HTTP requests - when(mockRequest.getRequestURI()).thenReturn(FAKE_URL); - - // Create an instance of the PubSubHome servlet - servletUnderTest = new PubSubPublish(); - - // Create a Topic with pull subscription - pubsub = PubSubOptions.getDefaultInstance().getService(); - TopicInfo topicInfo = TopicInfo.newBuilder(topicName).build(); - pubsub.create(topicInfo); - - // Pull subscription - SubscriptionInfo subscriptionInfo = SubscriptionInfo.newBuilder( - topicName, subscriptionName).build(); - pubsub.create(subscriptionInfo); - } - - @After - public void tearDown() throws Exception { - // Delete Topic and Subscription - pubsub.deleteSubscription(subscriptionName); - pubsub.deleteTopic(topicName); - } - - @Test - public void doPostIT() throws Exception { - // Dummy payload - final String dummyPayload = "MessageToPost"; - - // Set payload data for request - when(mockRequest.getParameter("payload")).thenReturn(dummyPayload); - - // Do POST - servletUnderTest.doPost(mockRequest, mockResponse); - - // Pull using subscription to verify publish - Iterator messages = pubsub.pull(subscriptionName, 1); - - // Check message payload is dummyPayload - if (messages.hasNext()) { - ReceivedMessage message = messages.next(); - assertThat(message.getPayloadAsString()) - .named("Publish Message") - .contains(dummyPayload); - message.ack(); - } - } -} \ No newline at end of file diff --git a/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPushTest.java b/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPushTest.java deleted file mode 100644 index b9296ac4d13..00000000000 --- a/flexible/pubsub/src/test/java/com/example/flexible/pubsub/PubSubPushTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/** - * 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.flexible.pubsub; - -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.when; - -import com.google.cloud.datastore.Datastore; -import com.google.cloud.datastore.DatastoreOptions; -import com.google.cloud.datastore.Entity; -import com.google.cloud.datastore.Key; -import com.google.cloud.datastore.Query; -import com.google.gson.Gson; -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.BufferedReader; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.Base64; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -@RunWith(JUnit4.class) -@SuppressWarnings("checkstyle:abbreviationaswordinname") -public class PubSubPushTest { - private static final String FAKE_URL = "fakeurl.google"; - private static final String ENTRY_KEY = "message_list"; - private static final String ENTRY_FIELD = "messages"; - @Mock private HttpServletRequest mockRequest; - @Mock private HttpServletResponse mockResponse; - private StringWriter responseWriter; - private PubSubPush servletUnderTest; - private String entryKind; - - @Before - public void setUp() throws Exception { - // Get environment variables - entryKind = System.getenv("DATASTORE_TEST_ENTRY_KIND"); - - // Initialize Mockito - MockitoAnnotations.initMocks(this); - - // Set up some fake HTTP requests - when(mockRequest.getRequestURI()).thenReturn(FAKE_URL); - - // Add token - String token = System.getenv("PUBSUB_VERIFICATION_TOKEN"); - when(mockRequest.getParameter("token")).thenReturn(token); - - // Set up a fake HTTP response - responseWriter = new StringWriter(); - - // Create an instance of the PubSubHome servlet - servletUnderTest = new PubSubPush(); - servletUnderTest.setTimeoutMilliSeconds(30000); - servletUnderTest.setEntryKind(entryKind); - } - - @Test - public void doPostSingleMessageIT() throws Exception { - // Clear all messages - clearMessageList(); - - // Mock reader for request - String message = "Hello, World!"; - BufferedReader mockReader = generatePostMessage(message); - when(mockRequest.getReader()).thenReturn(mockReader); - - // Expected output - final List expectedMessageList = new LinkedList<>(); - expectedMessageList.add(message); - - // Do POST - servletUnderTest.doPost(mockRequest, mockResponse); - - // Test that Message exists in Datastore - List messages = getMessages(); - assertThat(messages).isEqualTo(expectedMessageList); - - // Clean up - clearMessageList(); - } - - private BufferedReader generatePostMessage(String message) { - String encodedPayload = Base64.getEncoder().encodeToString( - message.getBytes()); - Map> messageObject = new HashMap<>(); - Map dataObject = new HashMap<>(); - messageObject.put("message", dataObject); - dataObject.put("data", encodedPayload); - - Gson gson = new Gson(); - StringReader reader = new StringReader(gson.toJson(messageObject, - messageObject.getClass())); - - return (new BufferedReader(reader)); - } - - private void clearMessageList() { - Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); - - // Clear all message - Key pushedMessages = datastore.newKeyFactory().setKind(entryKind) - .newKey(ENTRY_KEY); - datastore.delete(pushedMessages); - } - - private List getMessages() { - Gson gson = new Gson(); - Datastore datastore = DatastoreOptions.getDefaultInstance().getService(); - Query query = Query.newEntityQueryBuilder().setKind(entryKind) - .setLimit(1).build(); - - Iterator entities = datastore.run(query); - List messages = new LinkedList<>(); - if (entities.hasNext()) { - Entity entity = entities.next(); - messages = gson.fromJson(entity.getString(ENTRY_FIELD), - messages.getClass()); - } - - return messages; - } -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index fbb54c24b76..1964cb048d2 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,7 @@ flexible/mailgun flexible/mailjet flexible/memcache + flexible/pubsub flexible/sendgrid flexible/sparkjava flexible/static-files From dc771c0315e455d18e73d3092bfbdb9e405470f9 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Wed, 5 Apr 2017 21:55:30 -0700 Subject: [PATCH 18/24] updating readme --- flexible/pubsub/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flexible/pubsub/README.md b/flexible/pubsub/README.md index 614ab817f31..6d64423875a 100644 --- a/flexible/pubsub/README.md +++ b/flexible/pubsub/README.md @@ -18,8 +18,7 @@ Create a topic gcloud beta pubsub topics create ``` -(Optional) Create a subscription, which includes specifying the endpoint to which the Pub/Sub server should send requests. -This is automatically done if the subscription by name : `` does not exist. +Create a subscription, which includes specifying the endpoint to which the Pub/Sub server should send requests. ``` gcloud beta pubsub subscriptions create \ --topic \ From fb45c4773ec8b5d89d531785b26d991cff205be4 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Tue, 18 Apr 2017 14:38:05 -0700 Subject: [PATCH 19/24] simplifying code --- flexible/pubsub/README.md | 4 +- flexible/pubsub/pom.xml | 20 +++-- flexible/pubsub/src/main/appengine/app.yaml | 6 +- .../com/example/flexible/pubsub/Message.java | 17 ++-- .../flexible/pubsub/MessageRepository.java | 79 ++--------------- .../pubsub/MessageRepositoryImpl.java | 86 +++++++++++++++++++ .../flexible/pubsub/PubSubPublish.java | 39 +++++++-- .../example/flexible/pubsub/PubSubPush.java | 34 ++++++-- .../flexible/pubsub/PubSubPublishTest.java | 48 +++++++++++ .../flexible/pubsub/PubSubPushTest.java | 63 ++++++++++++++ flexible/pubsub/src/main/webapp/index.jsp | 2 +- 11 files changed, 291 insertions(+), 107 deletions(-) create mode 100644 flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java create mode 100644 flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPublishTest.java create mode 100644 flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java diff --git a/flexible/pubsub/README.md b/flexible/pubsub/README.md index 6d64423875a..84c3efdff0a 100644 --- a/flexible/pubsub/README.md +++ b/flexible/pubsub/README.md @@ -18,7 +18,7 @@ Create a topic gcloud beta pubsub topics create ``` -Create a subscription, which includes specifying the endpoint to which the Pub/Sub server should send requests. +Create a subscription, which includes specifying the endpoint to which the Pub/Sub server should send requests. ``` gcloud beta pubsub subscriptions create \ --topic \ @@ -29,7 +29,7 @@ gcloud beta pubsub subscriptions create \ ## Run Set the following environment variables and run using shown Maven command. You can then -direct your browser to `http://localhost:8080/` +direct your browser to `http://localhost:8080/` ``` export PUBSUB_TOPIC= diff --git a/flexible/pubsub/pom.xml b/flexible/pubsub/pom.xml index a128dfce697..727f9a6bee2 100644 --- a/flexible/pubsub/pom.xml +++ b/flexible/pubsub/pom.xml @@ -1,5 +1,5 @@ @@ -71,14 +71,11 @@ 1.9.38 test - - com.google.truth - truth - 0.30 - test + org.eclipse.jetty + jetty-server + 9.4.3.v20170317 - junit junit @@ -90,6 +87,11 @@ 1.10.19 test + + org.eclipse.jetty + jetty-servlet + 9.3.14.v20161028 + diff --git a/flexible/pubsub/src/main/appengine/app.yaml b/flexible/pubsub/src/main/appengine/app.yaml index f9dd149e744..aab1af55412 100644 --- a/flexible/pubsub/src/main/appengine/app.yaml +++ b/flexible/pubsub/src/main/appengine/app.yaml @@ -21,8 +21,8 @@ handlers: # [START env_variables] env_variables: - PUBSUB_TOPIC: test-topic-1 - PUBSUB_VERIFICATION_TOKEN: verification-token-123234 - PUBSUB_SUBSCRIPTION_NAME : subscription-name-1 + PUBSUB_TOPIC: + PUBSUB_VERIFICATION_TOKEN: + PUBSUB_SUBSCRIPTION_NAME : # [END env_variables] # [END appyaml] diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java index a7aaa8de076..bff654621a1 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java @@ -18,22 +18,20 @@ * persisted in storage. */ public class Message { - private String id; + private String messageId; private String publishTime; private String data; - public Message() {} - - public Message(String id) { - this.id = id; + public Message(String messageId) { + this.messageId = messageId; } - public String getId() { - return id; + public String getMessageId() { + return messageId; } - public void setId(String id) { - this.id = id; + public void setMessageId(String messageId) { + this.messageId = messageId; } public String getPublishTime() { @@ -52,3 +50,4 @@ public void setData(String data) { this.data = data; } } + diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java index 59c72a3c703..ae6cded5eb4 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java @@ -13,86 +13,21 @@ */ package com.example.flexible.pubsub; -import com.google.cloud.datastore.*; -import java.util.ArrayList; -import java.util.Base64; import java.util.List; -/** Storage for Message objects using Cloud Datastore. */ -public class MessageRepository { - private static String messagesKind = "messages"; - private KeyFactory keyFactory = getDatastoreInstance().newKeyFactory().setKind(messagesKind); +public interface MessageRepository { - /** Create a lazy initialized singleton of MessageRepository. */ - private static class LazyInit { - private static final MessageRepository INSTANCE; - - static { - INSTANCE = new MessageRepository(); - } - } - - public static MessageRepository getInstance() { - return LazyInit.INSTANCE; - } - - /** Save message id, data (decoded from base64), publish time */ - public void save(Message message) { - // Save message to "messages" - Datastore datastore = getDatastoreInstance(); - Key key = datastore.allocateId(keyFactory.newKey()); - - Entity.Builder messageEntityBuilder = Entity.newBuilder(key).set("messageId", message.getId()); - - if (message.getData() != null) { - messageEntityBuilder = messageEntityBuilder.set("data", decode(message.getData())); - } - - if (message.getPublishTime() != null) { - messageEntityBuilder = messageEntityBuilder.set("publishTime", message.getPublishTime()); - } - datastore.put(messageEntityBuilder.build()); - } + /** Save message to persistent storage */ + void save(Message message); /** - * Retrieve most recent messages. - * + * Retrieve most recent stored messages. * @param limit number of messages * @return list of messages */ - public List retrieve(int limit) { - // Get Message saved in Datastore - Datastore datastore = getDatastoreInstance(); - Query query = - Query.newEntityQueryBuilder() - .setKind(messagesKind) - .setLimit(limit) - .addOrderBy(StructuredQuery.OrderBy.desc("publishTime")) - .build(); - QueryResults results = datastore.run(query); + List retrieve(int limit); +} + - List messages = new ArrayList<>(); - while (results.hasNext()) { - Entity entity = results.next(); - Message message = new Message(entity.getString("messageId")); - String data = entity.getString("data"); - if (data != null) { - message.setData(data); - } - String publishTime = entity.getString("publishTime"); - if (publishTime != null) { - message.setPublishTime(publishTime); - } - messages.add(message); - } - return messages; - } - private String decode(String data) { - return new String(Base64.getDecoder().decode(data)); - } - private Datastore getDatastoreInstance() { - return DatastoreOptions.getDefaultInstance().getService(); - } -} diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java new file mode 100644 index 00000000000..475c4359f41 --- /dev/null +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java @@ -0,0 +1,86 @@ +/** + * Copyright 2017 Google Inc. + * + *

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.flexible.pubsub; + +import com.google.cloud.datastore.Datastore; +import com.google.cloud.datastore.DatastoreOptions; +import com.google.cloud.datastore.Entity; +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.cloud.datastore.StructuredQuery; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +/** Storage for Message objects using Cloud Datastore. */ +public class MessageRepositoryImpl implements MessageRepository { + + private String messagesKind = "messages"; + private KeyFactory keyFactory = getDatastoreInstance().newKeyFactory().setKind(messagesKind); + + @Override + public void save(Message message) { + // Save message to "messages" + Datastore datastore = getDatastoreInstance(); + Key key = datastore.allocateId(keyFactory.newKey()); + + Entity.Builder messageEntityBuilder = Entity.newBuilder(key) + .set("messageId", message.getMessageId()); + + if (message.getData() != null) { + messageEntityBuilder = messageEntityBuilder.set("data", message.getData()); + } + + if (message.getPublishTime() != null) { + messageEntityBuilder = messageEntityBuilder.set("publishTime", message.getPublishTime()); + } + datastore.put(messageEntityBuilder.build()); + } + + @Override + public List retrieve(int limit) { + // Get Message saved in Datastore + Datastore datastore = getDatastoreInstance(); + Query query = + Query.newEntityQueryBuilder() + .setKind(messagesKind) + .setLimit(limit) + .addOrderBy(StructuredQuery.OrderBy.desc("publishTime")) + .build(); + QueryResults results = datastore.run(query); + + List messages = new ArrayList<>(); + while (results.hasNext()) { + Entity entity = results.next(); + Message message = new Message(entity.getString("messageId")); + String data = entity.getString("data"); + if (data != null) { + message.setData(data); + } + String publishTime = entity.getString("publishTime"); + if (publishTime != null) { + message.setPublishTime(publishTime); + } + messages.add(message); + } + return messages; + } + + private Datastore getDatastoreInstance() { + return DatastoreOptions.getDefaultInstance().getService(); + } +} diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java index c33e7b2f527..a4ba8dbf055 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java @@ -13,27 +13,56 @@ */ package com.example.flexible.pubsub; +import com.google.cloud.ServiceOptions; +import com.google.cloud.pubsub.spi.v1.Publisher; +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; +import com.google.pubsub.v1.TopicName; +import org.apache.http.HttpStatus; + import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.http.HttpStatus; @WebServlet(name = "Publish with PubSub", value = "/pubsub/publish") public class PubSubPublish extends HttpServlet { + + private Publisher publisher; + + public PubSubPublish() { + } + + PubSubPublish(Publisher publisher) { + this.publisher = publisher; + } + @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - final String payload = req.getParameter("payload"); - - PubSubService pubSubService = PubSubService.getInstance(); + Publisher publisher = this.publisher; try { - pubSubService.publish(payload); + String topicId = System.getenv("PUBSUB_TOPIC"); + // create a publisher on the topic + if (publisher == null) { + publisher = Publisher.defaultBuilder( + TopicName.create(ServiceOptions.getDefaultProjectId(), topicId)) + .build(); + } + // construct a pubsub message from the payload + final String payload = req.getParameter("payload"); + PubsubMessage pubsubMessage = + PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8(payload)).build(); + + publisher.publish(pubsubMessage); resp.sendRedirect("/"); } catch (Exception e) { resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } } + } + + diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java index 469b33e8d08..6af36da107b 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java @@ -13,8 +13,12 @@ */ package com.example.flexible.pubsub; -import com.google.gson.*; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + import java.io.IOException; +import java.util.Base64; import java.util.stream.Collectors; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; @@ -25,9 +29,17 @@ @WebServlet(name = "Push with PubSub", value = "/pubsub/push") public class PubSubPush extends HttpServlet { - private final MessageRepository messageRepository = MessageRepository.getInstance(); private final Gson gson = new Gson(); private final JsonParser jsonParser = new JsonParser(); + private MessageRepository messageRepository; + + PubSubPush(MessageRepository messageRepository) { + this.messageRepository = messageRepository; + } + + public PubSubPush() { + this.messageRepository = new MessageRepositoryImpl(); + } @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) @@ -38,20 +50,30 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); return; } + // parse message object from "message" field in the request body json Message message = getMessage(req); try { messageRepository.save(message); // 200, 201, 204, 102 status codes are interpreted as success by the Pub/Sub system - resp.setStatus(200); + resp.setStatus(HttpServletResponse.SC_OK); } catch (Exception e) { - resp.setStatus(500); + resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } private Message getMessage(HttpServletRequest request) throws IOException { String requestBody = request.getReader().lines().collect(Collectors.joining("\n")); JsonElement jsonRoot = jsonParser.parse(requestBody); - String message = jsonRoot.getAsJsonObject().get("message").toString(); - return gson.fromJson(message, Message.class); + String messageStr = jsonRoot.getAsJsonObject().get("message").toString(); + Message message = gson.fromJson(messageStr, Message.class); + // decode from base64 + String decoded = decode(message.getData()); + message.setData(decoded); + return message; + } + + private String decode(String data) { + return new String(Base64.getDecoder().decode(data)); } } + diff --git a/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPublishTest.java b/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPublishTest.java new file mode 100644 index 00000000000..1c17d7a7233 --- /dev/null +++ b/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPublishTest.java @@ -0,0 +1,48 @@ +/** + * Copyright 2017 Google Inc. + * + *

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.flexible.pubsub; + +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.gax.core.SettableApiFuture; +import com.google.cloud.pubsub.spi.v1.Publisher; +import com.google.protobuf.ByteString; +import com.google.pubsub.v1.PubsubMessage; +import org.junit.Test; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class PubSubPublishTest { + + @Test + public void servletPublishesPayloadMessage() throws Exception { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getParameter("payload")).thenReturn("test-message"); + + HttpServletResponse response = mock(HttpServletResponse.class); + Publisher publisher = mock(Publisher.class); + PubsubMessage message = PubsubMessage.newBuilder() + .setData(ByteString.copyFromUtf8("test-message")).build(); + when(publisher.publish(eq(message))).thenReturn(SettableApiFuture.create()); + PubSubPublish pubSubPublish = new PubSubPublish(publisher); + // verify content of published test message + pubSubPublish.doPost(request, response); + verify(publisher, times(1)).publish(eq(message)); + } +} diff --git a/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java b/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java new file mode 100644 index 00000000000..95ddc68d861 --- /dev/null +++ b/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java @@ -0,0 +1,63 @@ +package com.example.flexible.pubsub; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; + +/** + * Copyright 2017 Google Inc. + * + *

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. + */ +import java.io.BufferedReader; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class PubSubPushTest { + + @Test + public void messageReceivedOverPushEndPointIsSaved() throws Exception { + MessageRepository messageRepository = mock(MessageRepository.class); + List messages = new ArrayList<>(); + doAnswer((invocation) -> { + messages.add((Message)invocation.getArguments()[0]); + return null; + } + ).when(messageRepository).save(any(Message.class)); + + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getParameter("token")) + .thenReturn(System.getenv("PUBSUB_VERIFICATION_TOKEN")); + + HttpServletResponse response = mock(HttpServletResponse.class); + BufferedReader reader = mock(BufferedReader.class); + when (request.getReader()).thenReturn(reader); + Stream requestBody = Stream.of( + "{\"message\":{\"data\":\"dGVzdA==\",\"attributes\":{}," + + "\"messageId\":\"91010751788941\",\"publishTime\":\"2017-04-05T23:16:42.302Z\"}}"); + when(reader.lines()).thenReturn(requestBody); + PubSubPush servlet = new PubSubPush(messageRepository); + assertEquals(messages.size(), 0); + servlet.doPost(request, response); + assertEquals(messages.size(), 1); + } +} + diff --git a/flexible/pubsub/src/main/webapp/index.jsp b/flexible/pubsub/src/main/webapp/index.jsp index d8b77c53a97..5cc76fca5b2 100644 --- a/flexible/pubsub/src/main/webapp/index.jsp +++ b/flexible/pubsub/src/main/webapp/index.jsp @@ -12,7 +12,7 @@

Last received messages

-<%! List messages = MessageRepository.getInstance().retrieve(10); %> +<%! List messages = MessageRepository.retrieve(10); %> From ba77db182682575a39137467358ed379d1db85e6 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Tue, 18 Apr 2017 14:48:06 -0700 Subject: [PATCH 20/24] cleanup, adding doc tags --- .../flexible/pubsub/MessageRepository.java | 1 + .../pubsub/MessageRepositoryImpl.java | 1 + .../flexible/pubsub/PubSubPublish.java | 25 ++-- .../example/flexible/pubsub/PubSubPush.java | 30 ++-- .../flexible/pubsub/PubSubService.java | 128 ------------------ 5 files changed, 35 insertions(+), 150 deletions(-) delete mode 100644 flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubService.java diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java index ae6cded5eb4..164d0548182 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java @@ -31,3 +31,4 @@ public interface MessageRepository { + diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java index 475c4359f41..b4ecef71920 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java @@ -84,3 +84,4 @@ private Datastore getDatastoreInstance() { return DatastoreOptions.getDefaultInstance().getService(); } } + diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java index a4ba8dbf055..202fc4082ff 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java @@ -27,18 +27,10 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +// [START publish] @WebServlet(name = "Publish with PubSub", value = "/pubsub/publish") public class PubSubPublish extends HttpServlet { - private Publisher publisher; - - public PubSubPublish() { - } - - PubSubPublish(Publisher publisher) { - this.publisher = publisher; - } - @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { @@ -57,12 +49,27 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8(payload)).build(); publisher.publish(pubsubMessage); + // redirect to home page resp.sendRedirect("/"); + // [END publish] } catch (Exception e) { resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } } + // ... +// [END publish] + + private Publisher publisher; + + public PubSubPublish() { + } + + PubSubPublish(Publisher publisher) { + this.publisher = publisher; + } } + + diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java index 6af36da107b..dc77ae58af4 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java @@ -26,21 +26,10 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -@WebServlet(name = "Push with PubSub", value = "/pubsub/push") +// [START push] +@WebServlet(value = "/pubsub/push") public class PubSubPush extends HttpServlet { - private final Gson gson = new Gson(); - private final JsonParser jsonParser = new JsonParser(); - private MessageRepository messageRepository; - - PubSubPush(MessageRepository messageRepository) { - this.messageRepository = messageRepository; - } - - public PubSubPush() { - this.messageRepository = new MessageRepositoryImpl(); - } - @Override public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { @@ -51,6 +40,7 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) return; } // parse message object from "message" field in the request body json + // decode message data from base64 Message message = getMessage(req); try { messageRepository.save(message); @@ -60,6 +50,8 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } + // ... +// [END push] private Message getMessage(HttpServletRequest request) throws IOException { String requestBody = request.getReader().lines().collect(Collectors.joining("\n")); @@ -75,5 +67,17 @@ private Message getMessage(HttpServletRequest request) throws IOException { private String decode(String data) { return new String(Base64.getDecoder().decode(data)); } + + private final Gson gson = new Gson(); + private final JsonParser jsonParser = new JsonParser(); + private MessageRepository messageRepository; + + PubSubPush(MessageRepository messageRepository) { + this.messageRepository = messageRepository; + } + + public PubSubPush() { + this.messageRepository = new MessageRepositoryImpl(); + } } diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubService.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubService.java deleted file mode 100644 index a0362bf68a9..00000000000 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubService.java +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright 2017 Google Inc. - * - *

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.flexible.pubsub; - -import com.google.api.gax.grpc.ApiException; -import com.google.cloud.ServiceOptions; -import com.google.cloud.pubsub.spi.v1.Publisher; -import com.google.cloud.pubsub.spi.v1.SubscriptionAdminClient; -import com.google.protobuf.ByteString; -import com.google.pubsub.v1.*; -import io.grpc.Status; -import java.util.logging.Logger; - -public class PubSubService { - - private Publisher publisher; - private Logger logger = Logger.getLogger(this.getClass().getName()); - - /** Create a lazy initialized singleton of PubSubService */ - private static class LazyInit { - private static final PubSubService INSTANCE; - - static { - System.out.println("instance"); - INSTANCE = new PubSubService(); - } - } - - public static PubSubService getInstance() { - return LazyInit.INSTANCE; - } - - /** - * Construct the push endpoint URL on appspot.com with verification token - * - * @return push endpoint - */ - private static String getPushEndPoint() { - return "https://" - + getProjectId() - + ".appspot.com/pubsub/push?token=" - + getPushVerificationToken(); - } - - private static String getPushVerificationToken() { - return System.getenv("PUBSUB_VERIFICATION_TOKEN"); - } - - /** - * Initialize the pubsub service : - create a publisher on a topic and add a subscriber with push - * endpoint for the topic - */ - private PubSubService() { - try { - String topicId = System.getenv("PUBSUB_TOPIC"); - String endpoint = getPushEndPoint(); - String subscriptionId = System.getenv("PUBSUB_SUBSCRIPTION_ID"); - - this.publisher = Publisher.newBuilder(getTopicName(topicId)).build(); - addPushEndPoint(topicId, subscriptionId, endpoint); - } catch (Exception e) { - logger.severe(e.toString()); - } - } - - /** - * Publish a message on configured topic - * - * @param message String - * @throws Exception - */ - public void publish(String message) throws Exception { - PubsubMessage pubsubMessage = - PubsubMessage.newBuilder().setData(ByteString.copyFromUtf8(message)).build(); - publisher.publish(pubsubMessage); - } - - private TopicName getTopicName(String topicId) { - return TopicName.create(getProjectId(), topicId); - } - - private SubscriptionName getSubscriptionName(String subscriptionId) { - return SubscriptionName.create(getProjectId(), subscriptionId); - } - - private static String getProjectId() { - return ServiceOptions.getDefaultProjectId(); - } - - /** - * Creates/modifies subscription with push configuration on topic - * - * @param topicId topic-id - * @param subscriptionId subscription-id - * @param endpoint push endpoint URL - * @throws Exception - */ - private void addPushEndPoint(String topicId, String subscriptionId, String endpoint) - throws Exception { - PushConfig pushConfig = PushConfig.newBuilder().setPushEndpoint(endpoint).build(); - SubscriptionName subscriptionName = getSubscriptionName(subscriptionId); - try (SubscriptionAdminClient subscriberAdminClient = SubscriptionAdminClient.create()) { - try { - subscriberAdminClient.createSubscription( - subscriptionName, getTopicName(topicId), pushConfig, 3); - } catch (ApiException e) { - // modify push config for existing subscription - if (e.getStatusCode().toStatus().equals(Status.ALREADY_EXISTS)) { - subscriberAdminClient.modifyPushConfig(subscriptionName, pushConfig); - return; - } - // otherwise, re-throw - throw e; - } - } - } -} From 2fe47ac564540c5af1a0c01de2efb2577c23a7c0 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Thu, 20 Apr 2017 13:09:20 -0700 Subject: [PATCH 21/24] bug fixes --- .../example/flexible/pubsub/MessageRepository.java | 2 +- .../flexible/pubsub/MessageRepositoryImpl.java | 12 +++++++++++- .../java/com/example/flexible/pubsub/PubSubPush.java | 2 +- flexible/pubsub/src/main/webapp/index.jsp | 6 +++--- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java index 164d0548182..b149a96f376 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java @@ -17,7 +17,7 @@ public interface MessageRepository { - /** Save message to persistent storage */ + /** Save message to persistent storage. */ void save(Message message); /** diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java index b4ecef71920..75f71a43f42 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java @@ -23,12 +23,13 @@ import com.google.cloud.datastore.StructuredQuery; import java.util.ArrayList; -import java.util.Base64; import java.util.List; /** Storage for Message objects using Cloud Datastore. */ public class MessageRepositoryImpl implements MessageRepository { + private static MessageRepositoryImpl instance; + private String messagesKind = "messages"; private KeyFactory keyFactory = getDatastoreInstance().newKeyFactory().setKind(messagesKind); @@ -83,5 +84,14 @@ public List retrieve(int limit) { private Datastore getDatastoreInstance() { return DatastoreOptions.getDefaultInstance().getService(); } + + // retrieve a singleton instance + public static synchronized MessageRepositoryImpl getInstance() { + if (instance == null) { + instance = new MessageRepositoryImpl(); + } + return instance; + } } + diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java index dc77ae58af4..6a5fd96989d 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java @@ -77,7 +77,7 @@ private String decode(String data) { } public PubSubPush() { - this.messageRepository = new MessageRepositoryImpl(); + this.messageRepository = MessageRepositoryImpl.getInstance(); } } diff --git a/flexible/pubsub/src/main/webapp/index.jsp b/flexible/pubsub/src/main/webapp/index.jsp index 5cc76fca5b2..6aab0e8692d 100644 --- a/flexible/pubsub/src/main/webapp/index.jsp +++ b/flexible/pubsub/src/main/webapp/index.jsp @@ -1,5 +1,5 @@ <%@ page import="com.example.flexible.pubsub.Message" %> -<%@ page import="com.example.flexible.pubsub.MessageRepository" %> +<%@ page import="com.example.flexible.pubsub.MessageRepositoryImpl" %> <%@ page import="java.util.List" %> @@ -12,7 +12,7 @@

Last received messages

-<%! List messages = MessageRepository.retrieve(10); %> +<%! List messages = MessageRepositoryImpl.getInstance().retrieve(10); %>
Id
@@ -22,7 +22,7 @@ <% for (Message message : messages) {%> - + From 58bf601637240e0622a41159dd07e80366211ba9 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Thu, 20 Apr 2017 16:42:50 -0700 Subject: [PATCH 22/24] bug fixes, adding PubSubHome --- flexible/pubsub/README.md | 3 +- flexible/pubsub/src/main/appengine/app.yaml | 1 - .../pubsub/MessageRepositoryImpl.java | 3 ++ .../example/flexible/pubsub/PubSubHome.java | 48 ++++++++++++++++++ .../flexible/pubsub/PubSubPublishTest.java | 2 + .../flexible/pubsub/PubSubPushTest.java | 1 + flexible/pubsub/src/main/webapp/index.jsp | 49 +++++++------------ 7 files changed, 74 insertions(+), 33 deletions(-) create mode 100644 flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java diff --git a/flexible/pubsub/README.md b/flexible/pubsub/README.md index 84c3efdff0a..0981c8d952f 100644 --- a/flexible/pubsub/README.md +++ b/flexible/pubsub/README.md @@ -34,7 +34,6 @@ direct your browser to `http://localhost:8080/` ``` export PUBSUB_TOPIC= export PUBSUB_VERIFICATION_TOKEN= -export PUBSUB_SUBSCRIPTION_ID= mvn jetty:run ``` @@ -48,7 +47,7 @@ curl -H "Content-Type: application/json" -i --data @sample_message.json ## Deploy -Update the environment variables `PUBSUB_TOPIC`, `PUBSUB_VERIFICATION_TOKEN` and `PUBSUB_SUBSCRIPTION_ID` in `app.yaml`, +Update the environment variables `PUBSUB_TOPIC` and `PUBSUB_VERIFICATION_TOKEN` in `app.yaml`, then: ``` diff --git a/flexible/pubsub/src/main/appengine/app.yaml b/flexible/pubsub/src/main/appengine/app.yaml index aab1af55412..c751f557f07 100644 --- a/flexible/pubsub/src/main/appengine/app.yaml +++ b/flexible/pubsub/src/main/appengine/app.yaml @@ -23,6 +23,5 @@ handlers: env_variables: PUBSUB_TOPIC: PUBSUB_VERIFICATION_TOKEN: - PUBSUB_SUBSCRIPTION_NAME : # [END env_variables] # [END appyaml] diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java index 75f71a43f42..4e47f2ff255 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java @@ -85,6 +85,9 @@ private Datastore getDatastoreInstance() { return DatastoreOptions.getDefaultInstance().getService(); } + private MessageRepositoryImpl() { + } + // retrieve a singleton instance public static synchronized MessageRepositoryImpl getInstance() { if (instance == null) { diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java new file mode 100644 index 00000000000..22d4a29c970 --- /dev/null +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java @@ -0,0 +1,48 @@ +/** + * Copyright 2017 Google Inc. + * + *

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.flexible.pubsub; + +import java.util.List; + +public class PubSubHome { + + private static MessageRepository messageRepository = MessageRepositoryImpl.getInstance(); + private static int MAX_MESSAGES = 10; + + /** + * Retrieve received messages in html. + * + * @return html representation of messages (one per row) + */ + public static String getReceivedMessages() { + List messageList = messageRepository.retrieve(MAX_MESSAGES); + return convertToHtmlTable(messageList); + } + + private static String convertToHtmlTable(List messages) { + StringBuilder sb = new StringBuilder(); + for (Message message : messages) { + sb.append("

"); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + } + return sb.toString(); + } + + private PubSubHome() { + } +} diff --git a/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPublishTest.java b/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPublishTest.java index 1c17d7a7233..6b7f06016e9 100644 --- a/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPublishTest.java +++ b/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPublishTest.java @@ -13,6 +13,7 @@ */ package com.example.flexible.pubsub; +import static org.junit.Assert.assertNotNull; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -32,6 +33,7 @@ public class PubSubPublishTest { @Test public void servletPublishesPayloadMessage() throws Exception { + assertNotNull(System.getenv("PUBSUB_TOPIC")); HttpServletRequest request = mock(HttpServletRequest.class); when(request.getParameter("payload")).thenReturn("test-message"); diff --git a/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java b/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java index 95ddc68d861..6836dd6d27f 100644 --- a/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java +++ b/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java @@ -44,6 +44,7 @@ public void messageReceivedOverPushEndPointIsSaved() throws Exception { ).when(messageRepository).save(any(Message.class)); HttpServletRequest request = mock(HttpServletRequest.class); + assertNotNull(System.getenv("PUBSUB_VERIFICATION_TOKEN")); when(request.getParameter("token")) .thenReturn(System.getenv("PUBSUB_VERIFICATION_TOKEN")); diff --git a/flexible/pubsub/src/main/webapp/index.jsp b/flexible/pubsub/src/main/webapp/index.jsp index 6aab0e8692d..07f68422454 100644 --- a/flexible/pubsub/src/main/webapp/index.jsp +++ b/flexible/pubsub/src/main/webapp/index.jsp @@ -1,32 +1,21 @@ -<%@ page import="com.example.flexible.pubsub.Message" %> -<%@ page import="com.example.flexible.pubsub.MessageRepositoryImpl" %> -<%@ page import="java.util.List" %> - +<%@ page import="com.example.flexible.pubsub.PubSubHome" %> -An example of using PubSub on App Engine Flex - -

Publish a message

- - - - - -

Last received messages

-<%! List messages = MessageRepositoryImpl.getInstance().retrieve(10); %> -
Id
>> > >
" + message.getMessageId() + "" + message.getData() + "" + message.getPublishTime() + "
- - - - - - <% - for (Message message : messages) {%> - - - - - - <%}%> -
IdMessagePublish time
>>>
- + An example of using PubSub on App Engine Flex + +

Publish a message

+
+ + + +
+

Last received messages

+ + + + + + + <%= PubSubHome.getReceivedMessages() %> +
IdDataPublishTime
+ \ No newline at end of file From 69c52b82d07371402d9fac8deeff951ec5f49791 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Thu, 20 Apr 2017 16:49:35 -0700 Subject: [PATCH 23/24] removing secure always --- flexible/pubsub/src/main/appengine/app.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/flexible/pubsub/src/main/appengine/app.yaml b/flexible/pubsub/src/main/appengine/app.yaml index c751f557f07..5233b58b21c 100644 --- a/flexible/pubsub/src/main/appengine/app.yaml +++ b/flexible/pubsub/src/main/appengine/app.yaml @@ -17,7 +17,6 @@ env: flex handlers: - url: /.* script: this field is required, but ignored - secure: always # [START env_variables] env_variables: From f9258efa92fb6422c396b89db4396bb832cd6fb9 Mon Sep 17 00:00:00 2001 From: Jisha Abubaker Date: Tue, 25 Apr 2017 09:46:10 -0700 Subject: [PATCH 24/24] review based fixes --- flexible/pubsub/README.md | 7 ++++--- flexible/pubsub/pom.xml | 4 ++-- .../main/java/com/example/flexible/pubsub/Message.java | 1 - .../com/example/flexible/pubsub/MessageRepository.java | 5 ----- .../example/flexible/pubsub/MessageRepositoryImpl.java | 2 -- .../java/com/example/flexible/pubsub/PubSubHome.java | 3 +-- .../java/com/example/flexible/pubsub/PubSubPublish.java | 9 +-------- .../java/com/example/flexible/pubsub/PubSubPush.java | 2 -- .../test/com/example/flexible/pubsub/PubSubPushTest.java | 1 - flexible/pubsub/src/main/webapp/index.jsp | 5 ++++- 10 files changed, 12 insertions(+), 27 deletions(-) diff --git a/flexible/pubsub/README.md b/flexible/pubsub/README.md index 0981c8d952f..a1706e14aad 100644 --- a/flexible/pubsub/README.md +++ b/flexible/pubsub/README.md @@ -11,14 +11,15 @@ cd java-docs-samples/flexible/pubsub ## Setup -Make sure `gcloud` is installed and authenticated. +Make sure [`gcloud`](https://cloud.google.com/sdk/docs/) is installed and authenticated. Create a topic ``` gcloud beta pubsub topics create ``` -Create a subscription, which includes specifying the endpoint to which the Pub/Sub server should send requests. +Create a push subscription, to send messages to a Google Cloud Project URL + such as https://.appspot.com/push. ``` gcloud beta pubsub subscriptions create \ --topic \ @@ -47,7 +48,7 @@ curl -H "Content-Type: application/json" -i --data @sample_message.json ## Deploy -Update the environment variables `PUBSUB_TOPIC` and `PUBSUB_VERIFICATION_TOKEN` in `app.yaml`, +Update the environment variables `PUBSUB_TOPIC` and `PUBSUB_VERIFICATION_TOKEN` in [`app.yaml`](src/main/appengine/app.yaml), then: ``` diff --git a/flexible/pubsub/pom.xml b/flexible/pubsub/pom.xml index 727f9a6bee2..c0ec8748ab8 100644 --- a/flexible/pubsub/pom.xml +++ b/flexible/pubsub/pom.xml @@ -29,7 +29,7 @@ - 1.0.0 + 1.3.0 1.8 1.8 false @@ -113,4 +113,4 @@
- \ No newline at end of file + diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java index bff654621a1..de2c7e9d85a 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/Message.java @@ -50,4 +50,3 @@ public void setData(String data) { this.data = data; } } - diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java index b149a96f376..2ec79d71c9a 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepository.java @@ -27,8 +27,3 @@ public interface MessageRepository { */ List retrieve(int limit); } - - - - - diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java index 4e47f2ff255..ff6e7eab137 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/MessageRepositoryImpl.java @@ -96,5 +96,3 @@ public static synchronized MessageRepositoryImpl getInstance() { return instance; } } - - diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java index 22d4a29c970..c65e3ed9e00 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubHome.java @@ -43,6 +43,5 @@ private static String convertToHtmlTable(List messages) { return sb.toString(); } - private PubSubHome() { - } + private PubSubHome() { } } diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java index 202fc4082ff..f4706faaf0e 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPublish.java @@ -56,20 +56,13 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) resp.sendError(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } } - - // ... // [END publish] private Publisher publisher; - public PubSubPublish() { - } + public PubSubPublish() { } PubSubPublish(Publisher publisher) { this.publisher = publisher; } } - - - - diff --git a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java index 6a5fd96989d..ce47254e7aa 100644 --- a/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java +++ b/flexible/pubsub/src/main/java/com/example/flexible/pubsub/PubSubPush.java @@ -50,7 +50,6 @@ public void doPost(HttpServletRequest req, HttpServletResponse resp) resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } } - // ... // [END push] private Message getMessage(HttpServletRequest request) throws IOException { @@ -80,4 +79,3 @@ public PubSubPush() { this.messageRepository = MessageRepositoryImpl.getInstance(); } } - diff --git a/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java b/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java index 6836dd6d27f..ebb84c3156c 100644 --- a/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java +++ b/flexible/pubsub/src/main/test/com/example/flexible/pubsub/PubSubPushTest.java @@ -42,7 +42,6 @@ public void messageReceivedOverPushEndPointIsSaved() throws Exception { return null; } ).when(messageRepository).save(any(Message.class)); - HttpServletRequest request = mock(HttpServletRequest.class); assertNotNull(System.getenv("PUBSUB_VERIFICATION_TOKEN")); when(request.getParameter("token")) diff --git a/flexible/pubsub/src/main/webapp/index.jsp b/flexible/pubsub/src/main/webapp/index.jsp index 07f68422454..fa12f02a14d 100644 --- a/flexible/pubsub/src/main/webapp/index.jsp +++ b/flexible/pubsub/src/main/webapp/index.jsp @@ -1,5 +1,8 @@ <%@ page import="com.example.flexible.pubsub.PubSubHome" %> + + + An example of using PubSub on App Engine Flex

Publish a message

@@ -18,4 +21,4 @@ <%= PubSubHome.getReceivedMessages() %> - \ No newline at end of file +