From a0dc25ba61020eb823946c54986b65288a026d83 Mon Sep 17 00:00:00 2001 From: Maxi Date: Mon, 4 Nov 2019 23:10:53 +0100 Subject: [PATCH] Sync * Completely revised communication between client and server. * Added synchronization functionality. * Added Message State updates * Added UserStatus updates --- .classpath | 6 - src/main/java/envoy/client/Client.java | 436 +++++++++++++++--- src/main/java/envoy/client/ui/ChatWindow.java | 61 ++- .../envoy/client/ui/MessageListRenderer.java | 11 +- .../envoy/client/ui/UserListRenderer.java | 25 +- 5 files changed, 434 insertions(+), 105 deletions(-) diff --git a/.classpath b/.classpath index d2bc4e0..906bfce 100644 --- a/.classpath +++ b/.classpath @@ -18,12 +18,6 @@ - - - - - - diff --git a/src/main/java/envoy/client/Client.java b/src/main/java/envoy/client/Client.java index 0ebce3c..b755c4a 100644 --- a/src/main/java/envoy/client/Client.java +++ b/src/main/java/envoy/client/Client.java @@ -1,8 +1,6 @@ package envoy.client; import java.time.Instant; -import java.util.Arrays; - import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; @@ -14,11 +12,10 @@ import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import envoy.schema.Message; -import envoy.schema.Message.MetaData.MessageState; -import envoy.schema.Messages; +import envoy.schema.Message.Metadata.MessageState; import envoy.schema.ObjectFactory; +import envoy.schema.Sync; import envoy.schema.User; -import envoy.schema.Users; /** * Project: envoy-client
@@ -38,6 +35,10 @@ public class Client { private Config config; private User sender, recipient; + private Sync unreadMessagesSync = objectFactory.createSync(); + public Sync sync = objectFactory.createSync(); + public Sync readMessages = objectFactory.createSync(); + public Client(Config config, String username) { this.config = config; try { @@ -46,43 +47,7 @@ public class Client { e.printStackTrace(); } sender = getUser(username); - } - - /** - * Sends a message with text content to the server.
- * Because sending a request is a blocking operation, it is executed - * asynchronously. - * - * @param sender name of the sender - * @param recipient name of the recipient - * @param textContent content (text) of the message - */ - public void sendMessage(Message message) { - new Thread(() -> { - // Wrap single message into messages list - Messages messages = wrapMessages(message); - - // Print message XML to console - JAXBContext jc; - try { - jc = JAXBContext.newInstance("envoy.schema"); - Marshaller m = jc.createMarshaller(); - m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); - m.marshal(messages, System.out); - } catch (JAXBException e) { - e.printStackTrace(); - } - - // Send message - javax.ws.rs.client.Client client = ClientBuilder.newClient(); - WebTarget target = client - .target(String.format("%s:%d/envoy-server/rest/message/send", config.getServer(), config.getPort())); - - Response response = target.request().post(Entity.entity(messages, "application/xml")); - System.out.println("Response code: " + response.getStatus()); - response.close(); - client.close(); - }).start(); + System.out.println("ID: " + sender.getID()); } /** @@ -92,7 +57,7 @@ public class Client { * @return prepared {@link Message} object */ public Message createMessage(String textContent) { - Message.MetaData metaData = objectFactory.createMessageMetaData(); + Message.Metadata metaData = objectFactory.createMessageMetadata(); metaData.setSender(sender.getID()); metaData.setRecipient(recipient.getID()); metaData.setState(MessageState.WAITING); @@ -103,16 +68,33 @@ public class Client { content.setText(textContent); Message message = objectFactory.createMessage(); - message.setMetaData(metaData); + message.setMetadata(metaData); message.getContent().add(content); return message; } + + /** + * Returns a {@link Sync} with all users on the server. + * @return Sync - List of all users on the server. + * @since Envoy v0.1-alpha + */ + public Sync getUsersListXml() { + Sync sendSync = objectFactory.createSync(); + User user = objectFactory.createUser(); + user.setID(-1); + sendSync.getUsers().add(user); - public Users getUsersListXml() { return get(String.format("%s:%d/envoy-server/rest/user", config.getServer(), config.getPort()), Users.class); } + javax.ws.rs.client.Client client = ClientBuilder.newClient(); + WebTarget target = client.target(String + .format("%s:%d/envoy-server/rest/sync/syncData?userId=%d", config.getServer(), config.getPort(), 0)); + + Response response = target.request().post(Entity.entity(sendSync, "application/xml")); + Sync returnSendSync = response.readEntity(Sync.class); + response.close(); + client.close(); + return returnSendSync; - public Messages getUnreadMessages(long userId) { - return get(String.format("%s:%d/envoy-server/rest/message/receive?userId=%d", config.getServer(), config.getPort(), userId), Messages.class); } /** @@ -123,52 +105,358 @@ public class Client { * @since Envoy v0.1-alpha */ private User getUser(String name) { - return get(String.format("%s:%d/envoy-server/rest/user/sender?name=%s", config.getServer(), config.getPort(), name), Users.class).getUser() - .get(0); - } + Sync senderSync = objectFactory.createSync(); + User user = objectFactory.createUser(); + user.setName(name); + senderSync.getUsers().add(user); - /** - * Invokes the GET method of a web service. - * - * @param the type of the object returned by the web service - * @param uri the URI of the web service - * @param responseClass the class of the object returned by the web service - * @return the object returned by the web service - * @since Envoy v0.1-alpha - */ - private T get(String uri, Class responseClass) { - javax.ws.rs.client.Client client = ClientBuilder.newClient(); - WebTarget target = client.target(uri); - Response response = target.request("application/xml").get(); - T responseObject = response.readEntity(responseClass); - System.out.println("Response code: " + response.getStatus()); + javax.ws.rs.client.Client client = ClientBuilder.newClient(); + WebTarget target = client.target(String + .format("%s:%d/envoy-server/rest/sync/syncData?userId=%d", config.getServer(), config.getPort(), 0)); + + Response response = target.request().post(Entity.entity(senderSync, "application/xml")); + User returnSender = objectFactory.createUser(); + Sync returnSenderSync = response.readEntity(Sync.class); + if (returnSenderSync.getUsers().size() == 1) { + returnSender = returnSenderSync.getUsers().get(0); + } else { + System.out.println("ERROR exiting..."); + } response.close(); client.close(); - return responseObject; + return returnSender; } /** - * Wraps one or more {@link Message} objects into a {@link Messages} object. + * Sends the "sync" Sync Object to the server and gets a "returnSync" Sync + * Object as response.
+ * It is also used to get the own sender at the start of the client + * (Client sends "sync" Sync Object with single user in it(name: the name entered at login, id: 0, UserStatus:null))
+ * and to get a complete list of all users saved on the server. + * (Client sends "sync" Sync Object with single user in it(name: "" (empty), id: -1, UserStatus:null))
+ * This method also processes the response Sync Object.
+ * It sorts its users and messages by specific variables and does certain things + * with them.
+ *
+ * Messages:
+ * -State SENT: Update Local message(s) with State WAITING (add Message ID and + * change State to SENT). (server sends these informations to the client if + * message(s) with State WAITING were successfully sent to the server)
+ * -State RECEIVED, SenderID != 0: Adds the unread Messages returned from the + * server in the latest sync to the "unreadMessagesSync" Sync Object.
+ * -State RECEIVED, SenderID == 0: Update message(s) in localDB to state RECEIVED. + * (server sends these informations to the client if the other client received + * the message(s).)
+ * -State READ: Update message(s) in the LocalDB to state READ. (server sends these informations to the client if the other client read + * the message(s).)
+ *
+ * Users:
+ * Updating UserStatus of all users in LocalDB. (Server sends all users with their updated UserStatus to the client.)
* - * @param messages the {@link Message} objects to wrap - * @return {@link Messages} object with all messages as its children + * @param userId + * @param localDB * @since Envoy v0.1-alpha */ - private Messages wrapMessages(Message... messages) { - Messages wrapper = objectFactory.createMessages(); - wrapper.getMessage().addAll(Arrays.asList(messages)); - return wrapper; + public void sendSync(long userId, LocalDB localDB) { + new Thread(() -> { + // Print sync XML to console + JAXBContext jc; + try { + jc = JAXBContext.newInstance("envoy.schema"); + Marshaller m = jc.createMarshaller(); + m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + m.marshal(sync, System.out); + } catch (JAXBException e) { + e.printStackTrace(); + } + addWaitingMessagesToSync(localDB); + + getSentStateMessagesFromLocalDB(localDB); + for (int i = 0; i < readMessages.getMessages().size(); i++) { + sync.getMessages().add(readMessages.getMessages().get(i)); + } + readMessages.getMessages().clear(); + + // Send sync + javax.ws.rs.client.Client client = ClientBuilder.newClient(); + WebTarget target = client + .target(String.format("%s:%d/envoy-server/rest/sync/syncData?userId=%d", + config.getServer(), + config.getPort(), + userId)); + + Response response = target.request().post(Entity.entity(sync, "application/xml")); + + Sync returnSync = response.readEntity(Sync.class); + if (returnSync.getMessages().size() != 0) { + System.out.println("Message ID: " + returnSync.getMessages().get(0).getMetadata().getMessageId()); + } + + for (int i = 0; i < returnSync.getMessages().size(); i++) { + + // Print sync XML to console + JAXBContext jc2; + try { + jc2 = JAXBContext.newInstance("envoy.schema"); + Marshaller m = jc2.createMarshaller(); + m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + m.marshal(returnSync, System.out); + } catch (JAXBException e) { + e.printStackTrace(); + } + + if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 + && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.SENT) { + // Update Local Messages with State WAITING (add Message ID and change State to + // SENT) + for (int j = 0; j < sync.getMessages().size(); j++) { + if (j == i) { + sync.getMessages() + .get(j) + .getMetadata() + .setMessageId(returnSync.getMessages().get(j).getMetadata().getMessageId()); + sync.getMessages() + .get(j) + .getMetadata() + .setState(returnSync.getMessages().get(j).getMetadata().getState()); + } + + } + + } + if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 + && returnSync.getMessages().get(i).getMetadata().getSender() != 0 + && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) { + // these are the unread Messages from the server + unreadMessagesSync.getMessages().add(returnSync.getMessages().get(i)); + + } + + if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 + && returnSync.getMessages().get(i).getMetadata().getSender() == 0 + && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) { + // Update Messages in localDB to state RECEIVED + for (int j = 0; j < localDB.getChats().size(); j++) { + if (localDB.getChats().get(j).getRecipient().getID() == returnSync.getMessages() + .get(i) + .getMetadata() + .getRecipient()) { + for (int k = 0; k < localDB.getChats().get(j).getModel().getSize(); k++) { + if (localDB.getChats() + .get(j) + .getModel() + .get(k) + .getMetadata() + .getMessageId() == returnSync.getMessages().get(i).getMetadata().getMessageId()) { + // Update Message in LocalDB + localDB.getChats() + .get(j) + .getModel() + .get(k) + .getMetadata() + .setState(returnSync.getMessages().get(j).getMetadata().getState()); + } + } + } + } + + } + + if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 + && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.READ) { + // Update local Messages to state READ + System.out + .println("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId() + + "was initialized to be set to READ in localDB."); + for (int j = 0; j < localDB.getChats().size(); j++) { + if (localDB.getChats().get(j).getRecipient().getID() == returnSync.getMessages() + .get(i) + .getMetadata() + .getRecipient()) { + System.out.println( + "Chat with: " + localDB.getChats().get(j).getRecipient().getID() + "was selected."); + for (int k = 0; k < localDB.getChats().get(j).getModel().getSize(); k++) { + if (localDB.getChats() + .get(j) + .getModel() + .get(k) + .getMetadata() + .getMessageId() == returnSync.getMessages().get(i).getMetadata().getMessageId()) { + System.out.println("Message with ID: " + + localDB.getChats().get(j).getModel().get(k).getMetadata().getMessageId() + + "was selected."); + localDB.getChats() + .get(j) + .getModel() + .get(k) + .getMetadata() + .setState(returnSync.getMessages().get(i).getMetadata().getState()); + System.out.println("Message State is now: " + localDB.getChats() + .get(j) + .getModel() + .get(k) + .getMetadata() + .getState() + .toString()); + } + } + } + } + + } + } + + // Updating UserStatus of all Users in LocalDB + for (int j = 0; j < returnSync.getUsers().size(); j++) { + for (int k = 0; k < localDB.getChats().size(); k++) { + if (localDB.getChats().get(k).getRecipient().getID() == returnSync.getUsers().get(j).getID()) { + + localDB.getChats().get(k).getRecipient().setStatus(returnSync.getUsers().get(j).getStatus()); + System.out.println(localDB.getChats().get(k).getRecipient().getStatus().toString()); + } + } + } + + System.out.println("Response code: " + response.getStatus()); + + response.close(); + client.close(); + sync.getMessages().clear(); + sync.getUsers().clear(); + + }).start(); + System.out.println(sync.getMessages().size()); + } /** - * @return the sender object that represents this client + * Adds a message to the "sync" Sync object. + * + * @param message + * @since Envoy v0.1-alpha + */ + public void addMessageToSync(Message message) { sync.getMessages().add(message); } + + /** + * Adds a user to the "sync" Sync object. + * + * @param user + * @since Envoy v0.1-alpha + */ + public void addUserToSync(User user) { sync.getUsers().add(user); } + + /** + * Adds the unread Messages returned from the server in the latest sync to the + * right chats in the LocalDB. + * + * @param localDB + * @since Envoy v0.1-alpha + */ + public void addUnreadMessagesToLocalDB(LocalDB localDB) { + Sync unreadMessages = unreadMessagesSync; + for (int i = 0; i < unreadMessages.getMessages().size(); i++) + for (int j = 0; j < localDB.getChats().size(); j++) + if (localDB.getChats().get(j).getRecipient().getID() == unreadMessages.getMessages() + .get(i) + .getMetadata() + .getSender()) { + localDB.getChats().get(j).appendMessage(unreadMessages.getMessages().get(i)); + } + } + + /** + * Gets all Messages with state SENT from the LocalDB and adds them to the + * "sync" Sync object. + * + * @param localDB + * @since Envoy v0.1-alpha + */ + public void getSentStateMessagesFromLocalDB(LocalDB localDB) { + for (int i = 0; i < localDB.getChats().size(); i++) { + for (int j = 0; j < localDB.getChats().get(i).getModel().getSize(); j++) { + if (localDB.getChats().get(i).getModel().get(j).getMetadata().getState() == MessageState.SENT) { + addMessageToSync(localDB.getChats().get(i).getModel().get(j)); + } + } + } + } + + /** + * Changes all Messages with State RECEIVED of a specific chat to State READ. + *
+ * Adds these Messages to the "readMessages" Sync object. + * + * @param currentChat + * @since Envoy v0.1-alpha + */ + public void setMessagesToRead(Chat currentChat) { + for (int j = 0; j < currentChat.getModel().getSize(); j++) { + if (currentChat.getModel().get(j).getMetadata().getRecipient() != currentChat.getRecipient().getID()) { + if (currentChat.getModel().get(j).getMetadata().getState() == MessageState.RECEIVED) { + currentChat.getModel().get(j).getMetadata().setState(MessageState.READ); + readMessages.getMessages().add(currentChat.getModel().get(j)); + } + } + } + } + + /** + * Adds a Message with State WAITING to a specific chat in the LocalDB. + * + * @param message + * @param currentChat + * @since Envoy v0.1-alpha + */ + public void addWaitingMessageToLocalDB(Message message, Chat currentChat) { currentChat.appendMessage(message); } + + /** + * Adds all Messages with State WAITING from the LocalDB to the Sync. + * + * @param localDB + * @since Envoy v0.1-alpha + */ + public void addWaitingMessagesToSync(LocalDB localDB) { + for (int i = 0; i < localDB.getChats().size(); i++) { + for (int j = 0; j < localDB.getChats().get(i).getModel().getSize(); j++) { + if (localDB.getChats().get(i).getModel().get(j).getMetadata().getState() == MessageState.WAITING) { + // addMessageToSync(localDB.getChats().get(i).getModel().get(j)); + System.out.println("Got Waiting Message"); + sync.getMessages().add(0, localDB.getChats().get(i).getModel().get(j)); + } + } + } + } + + /** + * @return the sender object that represents this client. * @since Envoy v0.1-alpha */ public User getSender() { return sender; } + /** + * @return the current recipient of the current chat. + * @since Envoy v0.1-alpha + */ public User getRecipient() { return recipient; } + /** + * Sets the recipient. + * + * @param recipient + * @since Envoy v0.1-alpha + */ public void setRecipient(User recipient) { this.recipient = recipient; } + /** + * @return boolean weather the recipient is currently set. + * @since Envoy v0.1-alpha + */ public boolean hasRecipient() { return recipient != null; } + + /** + * Clears the "unreadMessagesSync" Sync object. + * + * @since Envoy v0.1-alpha + */ + public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); } } \ No newline at end of file diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index 5f79b13..4ac1a6c 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -27,9 +27,9 @@ import envoy.client.Chat; import envoy.client.Client; import envoy.client.LocalDB; import envoy.schema.Message; -import envoy.schema.Messages; +import envoy.schema.Sync; import envoy.schema.User; -import envoy.schema.Users; + /** * Project: envoy-client
@@ -147,18 +147,17 @@ public class ChatWindow extends JFrame { postButton.addActionListener((evt) -> { if (!client.hasRecipient()) { - JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE); - return; + JOptionPane.showMessageDialog(this, + "Please select a recipient!", + "Cannot send message", + JOptionPane.INFORMATION_MESSAGE); } if (!messageEnterTextfield.getText().isEmpty()) try { // Create and send message object final Message message = client.createMessage(messageEnterTextfield.getText()); - client.sendMessage(message); - - // Append message object to chat - currentChat.appendMessage(message); + client.addWaitingMessageToLocalDB(message, currentChat); messageList.setModel(currentChat.getModel()); // Clear text field @@ -200,16 +199,23 @@ public class ChatWindow extends JFrame { final User user = selectedUserList.getSelectedValue(); client.setRecipient(user); - currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getID() == user.getID()).findFirst().get(); + currentChat = localDB.getChats() + .stream() + .filter(chat -> chat.getRecipient().getID() == user.getID()) + .findFirst() + .get(); client.setRecipient(user); textPane.setText(currentChat.getRecipient().getName()); + messageList.setModel(currentChat.getModel()); contentPane.revalidate(); } }); - + + + userList.setSelectionForeground(new Color(255, 255, 255)); userList.setSelectionBackground(new Color(102, 0, 153)); userList.setForeground(new Color(255, 255, 255)); @@ -227,6 +233,7 @@ public class ChatWindow extends JFrame { contentPane.add(userList, gbc_userList); contentPane.revalidate(); + loadUsersAndChats(); startReceiverThread(5000); @@ -239,9 +246,9 @@ public class ChatWindow extends JFrame { */ private void loadUsersAndChats() { new Thread(() -> { - Users users = client.getUsersListXml(); + Sync users = client.getUsersListXml(); DefaultListModel userListModel = new DefaultListModel<>(); - users.getUser().forEach(user -> { + users.getUsers().forEach(user -> { userListModel.addElement(user); // Check if user exists in local DB @@ -253,18 +260,34 @@ public class ChatWindow extends JFrame { } /** - * Checks for new messages and adds them to the chat list. + * For detailed information see Javadoc of corresponding methods. * * @param timeout the amount of time that passes between two requests sent to * the server + * @since Envoy v0.1-alpha */ private void startReceiverThread(int timeout) { new Timer(timeout, (evt) -> { - Messages unreadMessages = client.getUnreadMessages(client.getSender().getID()); - for (int i = 0; i < unreadMessages.getMessage().size(); i++) - for (int j = 0; j < localDB.getChats().size(); j++) - if (localDB.getChats().get(j).getRecipient().getID() == unreadMessages.getMessage().get(i).getMetaData().getSender()) - localDB.getChats().get(j).appendMessage(unreadMessages.getMessage().get(i)); - }).start(); + if(currentChat != null) { + client.setMessagesToRead(currentChat); + } + client.sendSync(client.getSender().getID(), localDB); + client.addUnreadMessagesToLocalDB(localDB); + client.clearUnreadMessagesSync(); + + for (int i = 0; i < userList.getModel().getSize(); i++) { + for (int j = 0; j < localDB.getChats().size(); j++) { + if(userList.getModel().getElementAt(i).getID() == localDB.getChats().get(j).getRecipient().getID()) { + userList.getModel().getElementAt(i).setStatus(localDB.getChats().get(j).getRecipient().getStatus()); + } + } + + } + + contentPane.revalidate(); + contentPane.repaint(); + userList.revalidate(); + userList.repaint(); + }).start(); } } \ No newline at end of file diff --git a/src/main/java/envoy/client/ui/MessageListRenderer.java b/src/main/java/envoy/client/ui/MessageListRenderer.java index 4490b69..7afdec3 100644 --- a/src/main/java/envoy/client/ui/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/MessageListRenderer.java @@ -18,6 +18,7 @@ import envoy.schema.Message; * Created: 19 Oct 2019
* * @author Kai S. K. Engelbart + * @author Maximilian Käfer * @since Envoy v0.1-alpha */ public class MessageListRenderer extends JLabel implements ListCellRenderer { @@ -38,14 +39,16 @@ public class MessageListRenderer extends JLabel implements ListCellRenderer

%s

%s", + "

%s

%s :%s", date, - text)); + text, + state)); return this; } } \ No newline at end of file diff --git a/src/main/java/envoy/client/ui/UserListRenderer.java b/src/main/java/envoy/client/ui/UserListRenderer.java index c0c13ba..40bb2ad 100644 --- a/src/main/java/envoy/client/ui/UserListRenderer.java +++ b/src/main/java/envoy/client/ui/UserListRenderer.java @@ -7,6 +7,7 @@ import javax.swing.JList; import javax.swing.ListCellRenderer; import envoy.schema.User; +import envoy.schema.User.UserStatus; /** * Defines how the {@code UserList} is displayed. @@ -16,6 +17,7 @@ import envoy.schema.User; * Created: 12 Oct 2019
* * @author Kai S. K. Engelbart + * @author Maximilian Käfer * @since Envoy v0.1-alpha */ public class UserListRenderer extends JLabel implements ListCellRenderer { @@ -36,8 +38,27 @@ public class UserListRenderer extends JLabel implements ListCellRenderer { // Enable background rendering setOpaque(true); - setText(value.getName()); - setFont(list.getFont()); + + final String name = value.getName(); + final UserStatus status = value.getStatus(); + + switch (status) { + case ONLINE: + setText(String.format( + "

%s

%s", + status, + name)); + break; + + case OFFLINE: + setText(String.format( + "

%s

%s", + status, + name)); + break; + } + + return this; }