diff --git a/src/main/java/shef/data/MessagePromise.java b/src/main/java/shef/data/MessagePromise.java index 7633d61..7588592 100644 --- a/src/main/java/shef/data/MessagePromise.java +++ b/src/main/java/shef/data/MessagePromise.java @@ -32,7 +32,6 @@ public MessagePromise(MessageUpdate messageUpdate) { this.message = null; this.updated = false; this.messageUpdate = messageUpdate; - this.messageUpdate.addObserver(this); } /** @@ -41,6 +40,8 @@ public MessagePromise(MessageUpdate messageUpdate) { * It then unblocks, and returns the message to the servlet. */ public synchronized String getNextMessage() { + messageUpdate.addObserver(this); + // Ensure that the thread waits until an update is detected. while (!updated) { try { @@ -51,6 +52,7 @@ public synchronized String getNextMessage() { } messageUpdate.deleteObserver(this); + this.updated = false; return message; } diff --git a/src/main/java/shef/data/MessageUpdate.java b/src/main/java/shef/data/MessageUpdate.java index 3d2a265..3651a28 100644 --- a/src/main/java/shef/data/MessageUpdate.java +++ b/src/main/java/shef/data/MessageUpdate.java @@ -31,14 +31,13 @@ public MessageUpdate() { } /** Sends the message to waiting observers via notifyObservers(). */ - public synchronized void sendMessage() { + public void sendMessage() { notifyObservers(message); } /** Set the message and mark the MessageUpdate as changed. */ - public synchronized void setMessage(String message) { - this.message = message; + public void setMessage(String message) { + this.message = message; setChanged(); } - -} \ No newline at end of file +} diff --git a/src/main/java/shef/servlets/GroupchatServlet.java b/src/main/java/shef/servlets/GroupchatServlet.java index a60b39d..f94d93d 100644 --- a/src/main/java/shef/servlets/GroupchatServlet.java +++ b/src/main/java/shef/servlets/GroupchatServlet.java @@ -27,6 +27,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; +import shef.data.Groupchat; + @WebServlet("/load-groupchat") public class GroupchatServlet extends HttpServlet { @@ -41,19 +43,11 @@ public void init() { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { String keyString = request.getParameter("key"); - Entity groupchatEntity = null; - try { - groupchatEntity = datastore.get(KeyFactory.stringToKey(keyString)); - } catch (EntityNotFoundException e) { - e.printStackTrace(); - return; - } + Groupchat group = new Groupchat(keyString); - Object messageObject = groupchatEntity.getProperty("messages"); - ArrayList messages = messageObject != null ? (ArrayList) messageObject : new ArrayList<>(); response.setContentType("application/json;"); Gson gson = new Gson(); - response.getWriter().println(gson.toJson(messages)); + response.getWriter().println(gson.toJson(group.getMessages())); } @Override diff --git a/src/main/java/shef/servlets/NewMessageServlet.java b/src/main/java/shef/servlets/NewMessageServlet.java index 9c13ed4..30aa07b 100644 --- a/src/main/java/shef/servlets/NewMessageServlet.java +++ b/src/main/java/shef/servlets/NewMessageServlet.java @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +/* + * The following source informed the architecture of this servlet and its functional components: + * https://docstore.mik.ua/orelly/java-ent/servlet/ch10_03.htm + */ + package shef.servlets; import com.google.appengine.api.datastore.DatastoreService; @@ -23,7 +28,13 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import shef.data.MessageUpdate; +import shef.data.MessagePromise; +import shef.data.Groupchat; +/** + * This servlet handles new messages, allowing for clients to see them as they're sent in real time. + * A client sends in new messages with doPost(), and doGet() distributes them to all other active clients. + */ @WebServlet("/new-message") public class NewMessageServlet extends HttpServlet { @@ -36,14 +47,44 @@ public void init() { datastore = DatastoreServiceFactory.getDatastoreService(); } + /** + * Waits for incoming messages using MessagePromises. + * Each GET request instantiates a MessagePromise, which blocks until a new message is received. + * The MessagePromise then unblocks and allows the GET request to respond with the new message. + */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + MessagePromise newMessagePromise = new MessagePromise(messageUpdate); + + // Blocks until the next message is received. + String newMessage = newMessagePromise.getNextMessage(); + response.setContentType("text/html"); + response.getWriter().println(newMessage); } + /** + * Posts new messages to Datastore and distributes them to active clients using MessageUpdates. + * The message is first added to the groupchat in Datastore, and then distributed to the waiting MessagePromises. + * Empty messages are ignored. + */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { - - } + String message = request.getParameter("message"); + if (message.equals("")) { + return; + } + // Upload new message to Datastore. + String keyString = request.getParameter("groupchat-key"); + Groupchat group = new Groupchat(keyString); + group.addMessage(message); + group.update(); + + // Send new message to waiting MessagePromises. + messageUpdate.setMessage(message); + messageUpdate.sendMessage(); + + response.setStatus(response.SC_NO_CONTENT); + } } diff --git a/src/main/webapp/groupchat.html b/src/main/webapp/groupchat.html index 24e1803..aa6d93a 100644 --- a/src/main/webapp/groupchat.html +++ b/src/main/webapp/groupchat.html @@ -5,10 +5,18 @@ shef - +

Groupchat

+
+ +
+
+ + + +
\ No newline at end of file diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html new file mode 100644 index 0000000..edcde94 --- /dev/null +++ b/src/main/webapp/index.html @@ -0,0 +1,23 @@ + + + + + + + shef + + + + +

Groupchat Options

+

Create new groupchat

+

+ +

Load groupchat

+ +

+ + +
+ + \ No newline at end of file diff --git a/src/main/webapp/script.js b/src/main/webapp/script.js index 47ca81f..412da7c 100644 --- a/src/main/webapp/script.js +++ b/src/main/webapp/script.js @@ -1,6 +1,6 @@ // Copyright 2019 Google LLC // -// Licensed under the Apache License, Version 2.0 (the "License"); +// 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 // @@ -38,3 +38,77 @@ function createCommentElement(comment) { function addParagraph(content) { return "

" + content + "

"; } + +/** Creates a new groupchat and redirects the client to the chatting page. */ +function newGroupchat() { + var request = new Request("/load-groupchat", {method: 'POST'}); + fetch(request).then(response => response.text()).then((key) => { + redirectToGroupchat(key); + }); +} + +/** Loads an existing groupchat. */ +function loadGroupchat() { + const urlParams = new URLSearchParams(window.location.search); + const key = urlParams.get('key'); + fetch('/load-groupchat?key=' + key) + .then(response => response.json(), error => { + alert('Error: Groupchat does not exist ' + error); + window.location.href = 'index.html'; + }).then((messages) => { + document.getElementById('messages').innerHTML = ''; + document.getElementById('groupchat-key').value = key; + for (var i = 0; i < messages.length; i++) { + addMessage(messages[i]); + } + getNextMessage(); + }); +} + +/** Posts a message to a groupchat. */ +function postNextMessage() { + const message = document.getElementById('message-input').value; + const groupKey = document.getElementById('groupchat-key').value; + var request = new Request('/new-message?message=' + message + '&groupchat-key=' + groupKey, {method: 'POST'}); + fetch(request).then( + document.getElementById('message-input').value = '' + ); +} + +/** + * Sends a request to the server to get the next message. + * This method is recursive (upon success) so that each client is always waiting for the next message. + * Requests use asynchronous Promises, so the client can perform other functions while waiting for a message. + */ +function getNextMessage() { + var request = new Request("/new-message", {method: 'GET'}) + fetch(request) + .then(response => { + if (response.ok) { + return response.text(); + } else { + throw new Error('Servlet closed'); + } + }).then(message => { + addMessage(message); + getNextMessage(); + }) + .catch(err => { + alert(err); + window.location.href = 'index.html'; + }); +} + +/** Given a groupchat key, redirects the client to that groupchat. */ +function redirectToGroupchat(keyParameter) { + const key = keyParameter ? keyParameter : document.getElementById('groupchat-key').value; + window.location.href = "/groupchat.html?key=" + key; +} + +/** Adds a

element containing a message to a groupchat. */ +function addMessage(message) { + var messagesContainer = document.getElementById('messages'); + const messageElement = document.createElement('p'); + messageElement.innerText = message; + messagesContainer.appendChild(messageElement); +}