Skip to content
This repository was archived by the owner on Jul 24, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3252f07
change MessageUpdate to use synchronization
tgomezzzz Jul 17, 2020
881b943
fix error handling within Promise's catch
tgomezzzz Jul 16, 2020
001cfc2
add form to groupchat.html to submit new messages
tgomezzzz Jul 16, 2020
772e2d0
add HTML pages for front end
tgomezzzz Jul 15, 2020
3c30a8d
add loading methods to javascript
tgomezzzz Jul 15, 2020
238b41a
create servlet to retrieve and post groupchats
tgomezzzz Jul 15, 2020
ba118ff
add MessageUpdate instance to NewMessageServlet
tgomezzzz Jul 15, 2020
ee248f1
implement MessageUpdate methods
tgomezzzz Jul 15, 2020
2466a1d
implement MessagePromise
tgomezzzz Jul 16, 2020
3b1b099
servlet always returns ArrayList instance instead of null in case of …
tgomezzzz Jul 16, 2020
e07de5f
change to public synchronized
tgomezzzz Jul 17, 2020
057d0e7
fix error handling within Promise's catch
tgomezzzz Jul 16, 2020
3267963
begin NewMessageServlet implementation
tgomezzzz Jul 17, 2020
fe1b289
change servlets to use Groupchat class
tgomezzzz Jul 17, 2020
ebf5a53
complete doPost
tgomezzzz Jul 17, 2020
e8f59fa
implement doGet
tgomezzzz Jul 17, 2020
5b50a04
add Javadoc
tgomezzzz Jul 17, 2020
84b429d
add debugging prints
tgomezzzz Jul 20, 2020
fbb5764
change HTML form to call JS function to submit messages
tgomezzzz Jul 21, 2020
480f613
override service() to handle botched requests
tgomezzzz Jul 21, 2020
8936647
print statements
tgomezzzz Jul 21, 2020
c6cd866
remove prints
tgomezzzz Jul 22, 2020
4f99ab9
rebase out, saving prints
tgomezzzz Jul 23, 2020
18d595e
clean print statements
tgomezzzz Jul 31, 2020
0571030
Merge branch 'feature-groupchat' into new-message-servlet
tgomezzzz Jul 31, 2020
cce9412
Merge branch 'feature-groupchat' into new-message-servlet
tgomezzzz Aug 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/main/java/shef/data/MessagePromise.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ public MessagePromise(MessageUpdate messageUpdate) {
this.message = null;
this.updated = false;
this.messageUpdate = messageUpdate;
this.messageUpdate.addObserver(this);
}

/**
Expand All @@ -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 {
Expand All @@ -51,6 +52,7 @@ public synchronized String getNextMessage() {
}

messageUpdate.deleteObserver(this);
this.updated = false;
return message;
}

Expand Down
9 changes: 4 additions & 5 deletions src/main/java/shef/data/MessageUpdate.java
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

}
}
14 changes: 4 additions & 10 deletions src/main/java/shef/servlets/GroupchatServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<String> messages = messageObject != null ? (ArrayList<String>) 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
Expand Down
45 changes: 43 additions & 2 deletions src/main/java/shef/servlets/NewMessageServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand All @@ -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);
}
}
10 changes: 9 additions & 1 deletion src/main/webapp/groupchat.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>shef</title>
<script type="text/javascript" src="script.js"></script>
<script type="text/javascript" src="script.js" onload="loadGroupchat()"></script>
</head>

<body>
<h1>Groupchat</h1>
<div id="messages"></div>

<form onsubmit="postNextMessage();return false" style="position: absolute; bottom: 10px; width: 100%">
<label for="message-input">Type a message:</label></br>
<textarea id="message-input" name="message" rows="5" style="width: 99%"></textarea>
<input type="hidden" name="groupchat-key" id="groupchat-key"/>
<input type="submit"/>
</form>
</body>
</html>
23 changes: 23 additions & 0 deletions src/main/webapp/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>shef</title>
<script type="text/javascript" src="script.js"></script>
</head>

<body>
<h1>Groupchat Options</h1>
<h3>Create new groupchat</h3>
<button onclick="newGroupchat()">New Groupchat</button></br></br>

<h3>Load groupchat</h3>
<label for="groupchat-key">Groupchat Key:</label>
<input type="text" id="groupchat-key"></input></br></br>
<button onclick="redirectToGroupchat()">Go!</button>

<div id="messages"></div>
</body>
</html>
76 changes: 75 additions & 1 deletion src/main/webapp/script.js
Original file line number Diff line number Diff line change
@@ -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
//
Expand Down Expand Up @@ -38,3 +38,77 @@ function createCommentElement(comment) {
function addParagraph(content) {
return "<p>" + content + "</p>";
}

/** 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 <p> 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);
}