Java tutorial
// 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 codeu.chat.server; import codeu.chat.database.Database; import codeu.chat.database.Packer; import codeu.chat.common.*; import codeu.chat.util.*; import codeu.chat.util.connections.Connection; import com.mongodb.client.model.Filters; import org.bson.Document; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.Collection; public final class Server { private static final Logger.Log LOG = Logger.newLog(Server.class); private static final int RELAY_REFRESH_MS = 5000; // 5 seconds private final Timeline timeline = new Timeline(); private final Uuid id; private final byte[] secret; private final String databaseName = "database"; private Database database; private final Model model = new Model(); private final View view = new View(model); private final Controller controller; private final Relay relay; private Uuid lastSeen = Uuid.NULL; public Server(Uuid id, byte[] secret, Relay relay) { this.id = id; this.secret = Arrays.copyOf(secret, secret.length); this.controller = new Controller(id, model); this.database = new Database(databaseName); // pull data from database LOG.info("Loading users"); for (User user : database.getUsers(100)) { controller.newUser(user.id, user.name, user.creation); } LOG.info("Loading conversations"); for (Conversation conversation : database.getConversations(100)) { controller.newConversation(conversation.id, conversation.title, conversation.owner, conversation.creation); } LOG.info("Loading messages"); for (Message message : database.getMessages(1000)) { controller.newMessage(message.id, message.author, message.conversation, message.content, message.creation); } this.relay = relay; timeline.scheduleNow(new Runnable() { @Override public void run() { try { LOG.info("Reading update from relay..."); for (final Relay.Bundle bundle : relay.read(id, secret, lastSeen, 32)) { onBundle(bundle); lastSeen = bundle.id(); } } catch (Exception ex) { LOG.error(ex, "Failed to read update from relay."); } timeline.scheduleIn(RELAY_REFRESH_MS, this); } }); } public User login(String username, String password) { LOG.info("Logging in " + username); Iterable<Document> foundDocs = database.users.find(Filters.eq("name", username)); LOG.info("Found user"); for (Document doc : foundDocs) { System.out.println(doc); if (password.equals(doc.get("password"))) { LOG.info("Successfully logged in " + username); return Packer.unpackUser(doc); } } return null; } public User signup(String username, String password) { Collection<User> users = database.findUser(username); if (users.isEmpty()) { User user = controller.newUser(username); if (database.writeUser(user, password)) { return user; } } return null; } public void handleConnection(final Connection connection) { timeline.scheduleNow(new Runnable() { @Override public void run() { try { LOG.info("Handling connection..."); final boolean success = onMessage(connection.in(), connection.out()); LOG.info("Connection handled: %s", success ? "ACCEPTED" : "REJECTED"); } catch (Exception ex) { LOG.error(ex, "Exception while handling connection."); } try { connection.close(); } catch (Exception ex) { LOG.error(ex, "Exception while closing connection."); } } }); } private boolean onMessage(InputStream in, OutputStream out) throws IOException { final int type = Serializers.INTEGER.read(in); if (type == NetworkCode.NEW_MESSAGE_REQUEST) { final Uuid author = Uuid.SERIALIZER.read(in); final Uuid conversation = Uuid.SERIALIZER.read(in); final String content = Serializers.STRING.read(in); final Message message = controller.newMessage(author, conversation, content); LOG.info("Writing new message to DB"); database.writeMessage(message); Serializers.INTEGER.write(out, NetworkCode.NEW_MESSAGE_RESPONSE); Serializers.nullable(Message.SERIALIZER).write(out, message); timeline.scheduleNow(createSendToRelayEvent(author, conversation, message.id)); } else if (type == NetworkCode.NEW_USER_REQUEST) { final String name = Serializers.STRING.read(in); final User user = controller.newUser(name); LOG.info("Writing new user to DB"); database.writeUser(user, "password"); Serializers.INTEGER.write(out, NetworkCode.NEW_USER_RESPONSE); Serializers.nullable(User.SERIALIZER).write(out, user); } else if (type == NetworkCode.NEW_CONVERSATION_REQUEST) { final String title = Serializers.STRING.read(in); final Uuid owner = Uuid.SERIALIZER.read(in); final Conversation conversation = controller.newConversation(title, owner); LOG.info("Writing new conversation to DB"); database.writeConversation(conversation); Serializers.INTEGER.write(out, NetworkCode.NEW_CONVERSATION_RESPONSE); Serializers.nullable(Conversation.SERIALIZER).write(out, conversation); } else if (type == NetworkCode.GET_USERS_BY_ID_REQUEST) { final Collection<Uuid> ids = Serializers.collection(Uuid.SERIALIZER).read(in); final Collection<User> users = view.getUsers(ids); Serializers.INTEGER.write(out, NetworkCode.GET_USERS_BY_ID_RESPONSE); Serializers.collection(User.SERIALIZER).write(out, users); } else if (type == NetworkCode.GET_ALL_CONVERSATIONS_REQUEST) { final Collection<ConversationSummary> conversations = view.getAllConversations(); Serializers.INTEGER.write(out, NetworkCode.GET_ALL_CONVERSATIONS_RESPONSE); Serializers.collection(ConversationSummary.SERIALIZER).write(out, conversations); } else if (type == NetworkCode.GET_CONVERSATIONS_BY_ID_REQUEST) { final Collection<Uuid> ids = Serializers.collection(Uuid.SERIALIZER).read(in); final Collection<Conversation> conversations = view.getConversations(ids); Serializers.INTEGER.write(out, NetworkCode.GET_CONVERSATIONS_BY_ID_RESPONSE); Serializers.collection(Conversation.SERIALIZER).write(out, conversations); } else if (type == NetworkCode.GET_MESSAGES_BY_ID_REQUEST) { final Collection<Uuid> ids = Serializers.collection(Uuid.SERIALIZER).read(in); final Collection<Message> messages = view.getMessages(ids); Serializers.INTEGER.write(out, NetworkCode.GET_MESSAGES_BY_ID_RESPONSE); Serializers.collection(Message.SERIALIZER).write(out, messages); } else if (type == NetworkCode.GET_USER_GENERATION_REQUEST) { Serializers.INTEGER.write(out, NetworkCode.GET_USER_GENERATION_RESPONSE); Uuid.SERIALIZER.write(out, view.getUserGeneration()); } else if (type == NetworkCode.GET_USERS_EXCLUDING_REQUEST) { final Collection<Uuid> ids = Serializers.collection(Uuid.SERIALIZER).read(in); final Collection<User> users = view.getUsersExcluding(ids); Serializers.INTEGER.write(out, NetworkCode.GET_USERS_EXCLUDING_RESPONSE); Serializers.collection(User.SERIALIZER).write(out, users); } else if (type == NetworkCode.GET_CONVERSATIONS_BY_TIME_REQUEST) { final Time startTime = Time.SERIALIZER.read(in); final Time endTime = Time.SERIALIZER.read(in); final Collection<Conversation> conversations = view.getConversations(startTime, endTime); Serializers.INTEGER.write(out, NetworkCode.GET_CONVERSATIONS_BY_TIME_RESPONSE); Serializers.collection(Conversation.SERIALIZER).write(out, conversations); } else if (type == NetworkCode.GET_CONVERSATIONS_BY_TITLE_REQUEST) { final String filter = Serializers.STRING.read(in); final Collection<Conversation> conversations = view.getConversations(filter); Serializers.INTEGER.write(out, NetworkCode.GET_CONVERSATIONS_BY_TITLE_RESPONSE); Serializers.collection(Conversation.SERIALIZER).write(out, conversations); } else if (type == NetworkCode.GET_MESSAGES_BY_TIME_REQUEST) { final Uuid conversation = Uuid.SERIALIZER.read(in); final Time startTime = Time.SERIALIZER.read(in); final Time endTime = Time.SERIALIZER.read(in); final Collection<Message> messages = view.getMessages(conversation, startTime, endTime); Serializers.INTEGER.write(out, NetworkCode.GET_MESSAGES_BY_TIME_RESPONSE); Serializers.collection(Message.SERIALIZER).write(out, messages); } else if (type == NetworkCode.GET_MESSAGES_BY_RANGE_REQUEST) { final Uuid rootMessage = Uuid.SERIALIZER.read(in); final int range = Serializers.INTEGER.read(in); final Collection<Message> messages = view.getMessages(rootMessage, range); Serializers.INTEGER.write(out, NetworkCode.GET_MESSAGES_BY_RANGE_RESPONSE); Serializers.collection(Message.SERIALIZER).write(out, messages); } else if (type == NetworkCode.LOGIN_REQUEST) { String username = Serializers.STRING.read(in); String password = Serializers.STRING.read(in); User loginResult = login(username, password); Serializers.INTEGER.write(out, NetworkCode.LOGIN_RESULT); Serializers.nullable(User.SERIALIZER).write(out, loginResult); } else if (type == NetworkCode.SIGNUP_REQUEST) { String username = Serializers.STRING.read(in); String password = Serializers.STRING.read(in); User signupResult = signup(username, password); Serializers.INTEGER.write(out, NetworkCode.SIGNUP_RESPONSE); Serializers.nullable(User.SERIALIZER).write(out, signupResult); } else { // In the case that the message was not handled make a dummy message with // the type "NO_MESSAGE" so that the client still gets something. Serializers.INTEGER.write(out, NetworkCode.NO_MESSAGE); } return true; } private void onBundle(Relay.Bundle bundle) { final Relay.Bundle.Component relayUser = bundle.user(); final Relay.Bundle.Component relayConversation = bundle.conversation(); final Relay.Bundle.Component relayMessage = bundle.user(); User user = model.userById().first(relayUser.id()); if (user == null) { user = controller.newUser(relayUser.id(), relayUser.text(), relayUser.time()); } Conversation conversation = model.conversationById().first(relayConversation.id()); if (conversation == null) { // As the relay does not tell us who made the conversation - the first person who // has a message in the conversation will get ownership over this server's copy // of the conversation. conversation = controller.newConversation(relayConversation.id(), relayConversation.text(), user.id, relayConversation.time()); } Message message = model.messageById().first(relayMessage.id()); if (message == null) { message = controller.newMessage(relayMessage.id(), user.id, conversation.id, relayMessage.text(), relayMessage.time()); } } private Runnable createSendToRelayEvent(final Uuid userId, final Uuid conversationId, final Uuid messageId) { return new Runnable() { @Override public void run() { final User user = view.findUser(userId); final Conversation conversation = view.findConversation(conversationId); final Message message = view.findMessage(messageId); relay.write(id, secret, relay.pack(user.id, user.name, user.creation), relay.pack(conversation.id, conversation.title, conversation.creation), relay.pack(message.id, message.content, message.creation)); } }; } }