From 892bb354fa89a6668e4d5fb77a72429c504d7a2b Mon Sep 17 00:00:00 2001 From: IlRomanenko Date: Sun, 12 Mar 2017 15:04:03 +0300 Subject: [PATCH 1/3] Added owner.md --- onwer.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 onwer.md diff --git a/onwer.md b/onwer.md new file mode 100644 index 00000000..a0574e98 --- /dev/null +++ b/onwer.md @@ -0,0 +1 @@ +Романенко Илья Игоревич \ No newline at end of file From 89fd1d403f90faf9c46fed9ca63c440cf308ea08 Mon Sep 17 00:00:00 2001 From: IlRomanenko Date: Tue, 11 Apr 2017 18:52:52 +0300 Subject: [PATCH 2/3] Messenger began --- .../java/track/lections/TcpEchoServer.java | 62 ++++-- .../track/lessons/l7threads/SimpleThread.java | 6 +- src/main/java/track/messenger/Chat.java | 10 + .../track/messenger/MessageServerMain.java | 25 +++ src/main/java/track/messenger/User.java | 36 ++++ .../messenger/commands/ChatCreateCommand.java | 17 ++ .../commands/ChatHistoryCommand.java | 17 ++ .../messenger/commands/ChatListCommand.java | 19 ++ .../track/messenger/commands/Command.java | 15 ++ .../messenger/commands/CommandFactory.java | 40 ++++ .../track/messenger/commands/InfoCommand.java | 17 ++ .../messenger/commands/LoginCommand.java | 22 +++ .../messenger/commands/UserCreateCommand.java | 20 ++ .../messenger/commands/UserListCommand.java | 17 ++ .../messenger/messages/LoginMessage.java | 21 ++ .../track/messenger/messages/Message.java | 45 +++++ .../track/messenger/messages/TextMessage.java | 49 +++++ .../java/track/messenger/messages/Type.java | 23 +++ .../track/messenger/net/BinaryProtocol.java | 41 ++++ .../track/messenger/net/MessengerServer.java | 84 ++++++++ .../java/track/messenger/net/Protocol.java | 22 +++ .../messenger/net/ProtocolException.java | 14 ++ .../java/track/messenger/net/Session.java | 67 +++++++ .../track/messenger/store/MessageStore.java | 38 ++++ .../java/track/messenger/store/UserStore.java | 30 +++ .../teacher/client/MessengerClient.java | 183 ++++++++++++++++++ 26 files changed, 917 insertions(+), 23 deletions(-) create mode 100644 src/main/java/track/messenger/Chat.java create mode 100644 src/main/java/track/messenger/MessageServerMain.java create mode 100644 src/main/java/track/messenger/User.java create mode 100644 src/main/java/track/messenger/commands/ChatCreateCommand.java create mode 100644 src/main/java/track/messenger/commands/ChatHistoryCommand.java create mode 100644 src/main/java/track/messenger/commands/ChatListCommand.java create mode 100644 src/main/java/track/messenger/commands/Command.java create mode 100644 src/main/java/track/messenger/commands/CommandFactory.java create mode 100644 src/main/java/track/messenger/commands/InfoCommand.java create mode 100644 src/main/java/track/messenger/commands/LoginCommand.java create mode 100644 src/main/java/track/messenger/commands/UserCreateCommand.java create mode 100644 src/main/java/track/messenger/commands/UserListCommand.java create mode 100644 src/main/java/track/messenger/messages/LoginMessage.java create mode 100644 src/main/java/track/messenger/messages/Message.java create mode 100644 src/main/java/track/messenger/messages/TextMessage.java create mode 100644 src/main/java/track/messenger/messages/Type.java create mode 100644 src/main/java/track/messenger/net/BinaryProtocol.java create mode 100644 src/main/java/track/messenger/net/MessengerServer.java create mode 100644 src/main/java/track/messenger/net/Protocol.java create mode 100644 src/main/java/track/messenger/net/ProtocolException.java create mode 100644 src/main/java/track/messenger/net/Session.java create mode 100644 src/main/java/track/messenger/store/MessageStore.java create mode 100644 src/main/java/track/messenger/store/UserStore.java create mode 100644 src/main/java/track/messenger/teacher/client/MessengerClient.java diff --git a/src/main/java/track/lections/TcpEchoServer.java b/src/main/java/track/lections/TcpEchoServer.java index 26d129b0..008cc700 100644 --- a/src/main/java/track/lections/TcpEchoServer.java +++ b/src/main/java/track/lections/TcpEchoServer.java @@ -1,15 +1,19 @@ package track.lections; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketAddress; - -public class TcpEchoServer { +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.SelectorProvider; +import java.util.Iterator; + +class TCPEchoServer { private static final int BUFSIZE = 32; + public static void main(String[] args) throws IOException { if (args.length != 1) { throw new IllegalArgumentException("Parameter: "); @@ -17,25 +21,43 @@ public static void main(String[] args) throws IOException { int servPort = Integer.parseInt(args[0]); - ServerSocket serverSocket = new ServerSocket(servPort); + Selector selector = SelectorProvider.provider().openSelector(); - int recvMsgSize; - byte[] recieveBuf = new byte[BUFSIZE]; + ServerSocketChannel serverChannel = ServerSocketChannel.open(); + serverChannel.configureBlocking(false); - while (true) { - Socket clntSock = serverSocket.accept(); + serverChannel.socket().bind(new InetSocketAddress("localhost", servPort)); - SocketAddress clientAddress = clntSock.getRemoteSocketAddress(); - System.out.println("Handling client at " + clientAddress); + serverChannel.register(selector, serverChannel.validOps()); - InputStream in = clntSock.getInputStream(); - OutputStream out = clntSock.getOutputStream(); - while ((recvMsgSize = in.read(recieveBuf)) != -1) { - out.write(recieveBuf, 0, recvMsgSize); - } + ByteBuffer recvBuffer = ByteBuffer.allocate(BUFSIZE); + + while (selector.select() > 0) { + - clntSock.close(); + Iterator iterator = selector.selectedKeys().iterator(); + + while (iterator.hasNext()) { + SelectionKey key = iterator.next(); + SocketChannel clientChannel; + + if (key.isAcceptable()) { + ServerSocketChannel clientServerChannel = (ServerSocketChannel) key.channel(); + clientChannel = clientServerChannel.accept(); + + clientChannel.configureBlocking(false); + clientChannel.register(selector, clientChannel.validOps()); + + } else if (key.isReadable()) { + clientChannel = (SocketChannel)key.channel(); + clientChannel.read(recvBuffer); + recvBuffer.flip(); + clientChannel.write(recvBuffer); + clientChannel.close(); + } + iterator.remove(); + } } } } diff --git a/src/main/java/track/lessons/l7threads/SimpleThread.java b/src/main/java/track/lessons/l7threads/SimpleThread.java index 78159ec1..76df9e57 100644 --- a/src/main/java/track/lessons/l7threads/SimpleThread.java +++ b/src/main/java/track/lessons/l7threads/SimpleThread.java @@ -39,10 +39,10 @@ public void run() { t1.start(); for (int i = 0; i < 5; i++) { - System.out.println("Main:" + i); + System.out.println("MessageServerMain:" + i); mysleep(1); } - System.out.println("Main thread finished"); + System.out.println("MessageServerMain thread finished"); } @@ -60,7 +60,7 @@ public void run() { thread.start(); System.out.println("Joining"); thread.join(); - System.out.println("Main exit"); + System.out.println("MessageServerMain exit"); } diff --git a/src/main/java/track/messenger/Chat.java b/src/main/java/track/messenger/Chat.java new file mode 100644 index 00000000..1fddf22e --- /dev/null +++ b/src/main/java/track/messenger/Chat.java @@ -0,0 +1,10 @@ +package track.messenger; + +/** + * Tehnotrack + * track.messenger + *

+ * Created by ilya on 11.04.17. + */ +public class Chat { +} diff --git a/src/main/java/track/messenger/MessageServerMain.java b/src/main/java/track/messenger/MessageServerMain.java new file mode 100644 index 00000000..1f68fff0 --- /dev/null +++ b/src/main/java/track/messenger/MessageServerMain.java @@ -0,0 +1,25 @@ +package track.messenger; + +import track.messenger.net.BinaryProtocol; +import track.messenger.net.MessengerServer; + +/** + * + */ +public class MessageServerMain { + + public static void main(String[] args) { + + if (args.length != 1) { + System.out.println("Usage "); + return; + } + + int port = Integer.parseInt(args[0]); + + MessengerServer server = new MessengerServer(2, new BinaryProtocol()); + + server.start(port); + + } +} diff --git a/src/main/java/track/messenger/User.java b/src/main/java/track/messenger/User.java new file mode 100644 index 00000000..e7e9ef8c --- /dev/null +++ b/src/main/java/track/messenger/User.java @@ -0,0 +1,36 @@ +package track.messenger; + +import java.util.List; + +/** + * Tehnotrack + * track.messenger + *

+ * Created by ilya on 11.04.17. + */ +public class User { + private long id; + private String nickname; + private List chatIds; + + public User(long id, String nickname) { + this.id = id; + this.nickname = nickname; + } + + public List getChatIds() { + return chatIds; + } + + public void setChatIds(List chatIds) { + this.chatIds = chatIds; + } + + public long getId() { + return id; + } + + public String getNickname() { + return nickname; + } +} diff --git a/src/main/java/track/messenger/commands/ChatCreateCommand.java b/src/main/java/track/messenger/commands/ChatCreateCommand.java new file mode 100644 index 00000000..5ed80ac7 --- /dev/null +++ b/src/main/java/track/messenger/commands/ChatCreateCommand.java @@ -0,0 +1,17 @@ +package track.messenger.commands; + +import track.messenger.messages.Message; +import track.messenger.net.Session; + +/** + * Tehnotrack + * track.messenger.commands + *

+ * Created by ilya on 11.04.17. + */ +public class ChatCreateCommand implements Command { + @Override + public void execute(Session session, Message message) { + + } +} diff --git a/src/main/java/track/messenger/commands/ChatHistoryCommand.java b/src/main/java/track/messenger/commands/ChatHistoryCommand.java new file mode 100644 index 00000000..6654326c --- /dev/null +++ b/src/main/java/track/messenger/commands/ChatHistoryCommand.java @@ -0,0 +1,17 @@ +package track.messenger.commands; + +import track.messenger.messages.Message; +import track.messenger.net.Session; + +/** + * Tehnotrack + * track.messenger.commands + *

+ * Created by ilya on 11.04.17. + */ +public class ChatHistoryCommand implements Command { + @Override + public void execute(Session session, Message message) { + + } +} diff --git a/src/main/java/track/messenger/commands/ChatListCommand.java b/src/main/java/track/messenger/commands/ChatListCommand.java new file mode 100644 index 00000000..26c5f2ce --- /dev/null +++ b/src/main/java/track/messenger/commands/ChatListCommand.java @@ -0,0 +1,19 @@ +package track.messenger.commands; + +import track.messenger.messages.Message; +import track.messenger.net.Session; + +/** + * Tehnotrack + * track.messenger.commands + *

+ * Created by ilya on 11.04.17. + */ +public class ChatListCommand implements Command { + + + @Override + public void execute(Session session, Message message) { + + } +} diff --git a/src/main/java/track/messenger/commands/Command.java b/src/main/java/track/messenger/commands/Command.java new file mode 100644 index 00000000..75804a14 --- /dev/null +++ b/src/main/java/track/messenger/commands/Command.java @@ -0,0 +1,15 @@ +package track.messenger.commands; + +import track.messenger.messages.Message; +import track.messenger.net.Session; + +/** + * Tehnotrack + * track.messenger.commands + *

+ * Created by ilya on 11.04.17. + */ +public interface Command { + + void execute(Session session, Message message); +} diff --git a/src/main/java/track/messenger/commands/CommandFactory.java b/src/main/java/track/messenger/commands/CommandFactory.java new file mode 100644 index 00000000..5ea5ce69 --- /dev/null +++ b/src/main/java/track/messenger/commands/CommandFactory.java @@ -0,0 +1,40 @@ +package track.messenger.commands; + +import track.messenger.messages.Type; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Tehnotrack + * track.messenger.commands + *

+ * Created by ilya on 11.04.17. + */ +public class CommandFactory { + + + private static Map commandMap; + + static { + Map tmpCommandMap = new HashMap<>(); + + + tmpCommandMap.put(Type.MSG_LOGIN, new LoginCommand()); + tmpCommandMap.put(Type.MSG_INFO, new InfoCommand()); + + tmpCommandMap.put(Type.MSG_CREATE_USER, new UserCreateCommand()); + tmpCommandMap.put(Type.MSG_USERS_LIST, new UserListCommand()); + + tmpCommandMap.put(Type.MSG_CHAT_CREATE, new ChatCreateCommand()); + tmpCommandMap.put(Type.MSG_CHAT_HIST, new ChatHistoryCommand()); + tmpCommandMap.put(Type.MSG_CHAT_LIST, new ChatListCommand()); + + commandMap = Collections.unmodifiableMap(tmpCommandMap); + } + + public static Command create(Type type) { + return commandMap.getOrDefault(type, null); + } +} diff --git a/src/main/java/track/messenger/commands/InfoCommand.java b/src/main/java/track/messenger/commands/InfoCommand.java new file mode 100644 index 00000000..2db80dd2 --- /dev/null +++ b/src/main/java/track/messenger/commands/InfoCommand.java @@ -0,0 +1,17 @@ +package track.messenger.commands; + +import track.messenger.messages.Message; +import track.messenger.net.Session; + +/** + * Tehnotrack + * track.messenger.commands + *

+ * Created by ilya on 11.04.17. + */ +public class InfoCommand implements Command { + @Override + public void execute(Session session, Message message) { + + } +} diff --git a/src/main/java/track/messenger/commands/LoginCommand.java b/src/main/java/track/messenger/commands/LoginCommand.java new file mode 100644 index 00000000..c3f23040 --- /dev/null +++ b/src/main/java/track/messenger/commands/LoginCommand.java @@ -0,0 +1,22 @@ +package track.messenger.commands; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import track.messenger.messages.Message; +import track.messenger.net.Session; + +/** + * Tehnotrack + * track.messenger.commands + *

+ * Created by ilya on 11.04.17. + */ +public class LoginCommand implements Command { + + private static Logger LOG = LoggerFactory.getLogger(LoginCommand.class); + + @Override + public void execute(Session session, Message message) { + LOG.info("Execute : ", message.toString()); + } +} diff --git a/src/main/java/track/messenger/commands/UserCreateCommand.java b/src/main/java/track/messenger/commands/UserCreateCommand.java new file mode 100644 index 00000000..5dc5a907 --- /dev/null +++ b/src/main/java/track/messenger/commands/UserCreateCommand.java @@ -0,0 +1,20 @@ +package track.messenger.commands; + +import track.messenger.messages.Message; +import track.messenger.net.Session; + +/** + * Tehnotrack + * track.messenger.commands + *

+ * Created by ilya on 11.04.17. + */ +public class UserCreateCommand implements Command { + + + + @Override + public void execute(Session session, Message message) { + + } +} diff --git a/src/main/java/track/messenger/commands/UserListCommand.java b/src/main/java/track/messenger/commands/UserListCommand.java new file mode 100644 index 00000000..86f317f3 --- /dev/null +++ b/src/main/java/track/messenger/commands/UserListCommand.java @@ -0,0 +1,17 @@ +package track.messenger.commands; + +import track.messenger.messages.Message; +import track.messenger.net.Session; + +/** + * Tehnotrack + * track.messenger.commands + *

+ * Created by ilya on 11.04.17. + */ +public class UserListCommand implements Command { + @Override + public void execute(Session session, Message message) { + + } +} diff --git a/src/main/java/track/messenger/messages/LoginMessage.java b/src/main/java/track/messenger/messages/LoginMessage.java new file mode 100644 index 00000000..cecfef50 --- /dev/null +++ b/src/main/java/track/messenger/messages/LoginMessage.java @@ -0,0 +1,21 @@ +package track.messenger.messages; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 11.04.17. + */ +public class LoginMessage extends Message{ + + private String login; + private String password; + + public LoginMessage(Long senderId, String login, String password) { + super(senderId, Type.MSG_LOGIN); + this.login = login; + this.password = password; + } + + +} diff --git a/src/main/java/track/messenger/messages/Message.java b/src/main/java/track/messenger/messages/Message.java new file mode 100644 index 00000000..e769044e --- /dev/null +++ b/src/main/java/track/messenger/messages/Message.java @@ -0,0 +1,45 @@ +package track.messenger.messages; + +import java.io.Serializable; + +/** + * + */ +public abstract class Message implements Serializable { + + private static volatile long ID_COUNTER = 0; + + private Long id; + private Long senderId; + private Type type; + + public Message(Long senderId, Type type) { + id = ID_COUNTER++; + this.senderId = senderId; + this.type = type; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getSenderId() { + return senderId; + } + + public void setSenderId(Long senderId) { + this.senderId = senderId; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } +} diff --git a/src/main/java/track/messenger/messages/TextMessage.java b/src/main/java/track/messenger/messages/TextMessage.java new file mode 100644 index 00000000..b804f2ed --- /dev/null +++ b/src/main/java/track/messenger/messages/TextMessage.java @@ -0,0 +1,49 @@ +package track.messenger.messages; + +import java.util.Objects; + +/** + * Простое текстовое сообщение + */ +public class TextMessage extends Message { + private String text; + + public TextMessage(Long senderId) { + super(senderId, Type.MSG_TEXT); + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + if (!super.equals(other)) { + return false; + } + TextMessage message = (TextMessage) other; + return Objects.equals(text, message.text); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), text); + } + + @Override + public String toString() { + return "TextMessage{" + + "text='" + text + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/track/messenger/messages/Type.java b/src/main/java/track/messenger/messages/Type.java new file mode 100644 index 00000000..e6bbdfcb --- /dev/null +++ b/src/main/java/track/messenger/messages/Type.java @@ -0,0 +1,23 @@ +package track.messenger.messages; + +/** + * Типы сообщений в системе + */ +public enum Type { + // Сообщения от клиента к серверу + MSG_CREATE_USER, // в ответ MSG_STATUS + auto login + MSG_LOGIN, // в ответ MSG_STATUS + MSG_TEXT, // в ответ MSG_STATUS + MSG_INFO, // в ответ MSG_INFO_RESULT + MSG_USERS_LIST, // в ответ MSG_USERS_LIST_RESULT + MSG_CHAT_LIST, // в ответ MSG_CHAT_LIST_RESULT, + MSG_CHAT_CREATE, // в ответ MSG_STATUS + MSG_CHAT_HIST, // в ответ MSG_CHAT_HIST_RESULT, + + // Сообщения от сервера клиенту + MSG_STATUS, + MSG_CHAT_LIST_RESULT, + MSG_CHAT_HIST_RESULT, + MSG_INFO_RESULT, + MSG_USERS_LIST_RESULT +} diff --git a/src/main/java/track/messenger/net/BinaryProtocol.java b/src/main/java/track/messenger/net/BinaryProtocol.java new file mode 100644 index 00000000..e718021b --- /dev/null +++ b/src/main/java/track/messenger/net/BinaryProtocol.java @@ -0,0 +1,41 @@ +package track.messenger.net; + +import org.apache.commons.lang3.SerializationUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import track.messenger.messages.Message; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Tehnotrack + * track.messenger.net + *

+ * Created by ilya on 10.04.17. + */ +public class BinaryProtocol implements Protocol { + + private static Logger LOG = LoggerFactory.getLogger(BinaryProtocol.class); + + @Override + public Message decode(InputStream stream) throws ProtocolException { + Message message = null; + try { + if (stream.available() > 0) { + message = SerializationUtils.deserialize(stream); + LOG.info("Decoded ", message); + } + } catch (IOException e) { + LOG.error(e.getMessage()); + throw new ProtocolException(e.getMessage()); + } + return message; + } + + @Override + public byte[] encode(Message msg) throws ProtocolException { + LOG.info("Encoded ", msg); + return SerializationUtils.serialize(msg); + } +} diff --git a/src/main/java/track/messenger/net/MessengerServer.java b/src/main/java/track/messenger/net/MessengerServer.java new file mode 100644 index 00000000..37c549c4 --- /dev/null +++ b/src/main/java/track/messenger/net/MessengerServer.java @@ -0,0 +1,84 @@ +package track.messenger.net; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import track.messenger.commands.Command; +import track.messenger.commands.CommandFactory; +import track.messenger.messages.Message; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.LinkedList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * + */ +public class MessengerServer { + + private static Logger LOG = LoggerFactory.getLogger(MessengerServer.class); + + private Protocol protocol; + private Thread acceptConnThread; + + private ExecutorService commandsPool; + + private volatile LinkedList sessions = new LinkedList<>(); + + private volatile boolean isAlive = true; + + public MessengerServer(int threadsCount, Protocol protocol) { + this.protocol = protocol; + commandsPool = Executors.newFixedThreadPool(threadsCount); + } + + public void start(int port) { + acceptConnThread = new Thread(() -> { + LOG.info(String.format("Start server on port %d", port)); + try { + acceptConnections(port); + } catch (IOException ex) { + LOG.error(ex.getMessage()); + isAlive = false; + } + }); + acceptConnThread.start(); + + while (isAlive) { + if (sessions.isEmpty()) { + continue; + } + Session session = sessions.getFirst(); + try { + Message msg = session.readMessage(); + if (msg == null) { + continue; + } + Command cmd = CommandFactory.create(msg.getType()); + if (cmd == null) { + continue; + } + commandsPool.submit(() -> cmd.execute(session, msg)); + } catch (ProtocolException e) { + LOG.error(e.getMessage()); + } + sessions.push(session); + } + + LOG.info("Stop server"); + acceptConnThread.stop(); + } + + private void acceptConnections(int port) throws IOException { + ServerSocket serverSocket = new ServerSocket(port); + + while (true) { + Socket client = serverSocket.accept(); + LOG.info("Accept connection"); + sessions.add(new Session(client, protocol)); + } + } + +} diff --git a/src/main/java/track/messenger/net/Protocol.java b/src/main/java/track/messenger/net/Protocol.java new file mode 100644 index 00000000..df1dafc0 --- /dev/null +++ b/src/main/java/track/messenger/net/Protocol.java @@ -0,0 +1,22 @@ +package track.messenger.net; + +import track.messenger.messages.Message; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +/** + * + */ +public interface Protocol { + + default Message decode(byte[] bytes) throws ProtocolException { + ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + return decode(bais); + } + + Message decode(InputStream stream) throws ProtocolException; + + byte[] encode(Message msg) throws ProtocolException; + +} diff --git a/src/main/java/track/messenger/net/ProtocolException.java b/src/main/java/track/messenger/net/ProtocolException.java new file mode 100644 index 00000000..aba5d538 --- /dev/null +++ b/src/main/java/track/messenger/net/ProtocolException.java @@ -0,0 +1,14 @@ +package track.messenger.net; + +/** + * Исключение, которое бросается, когда происходят ошибки кодирования/декодирования + */ +public class ProtocolException extends Exception { + public ProtocolException(String msg) { + super(msg); + } + + public ProtocolException(Throwable ex) { + super(ex); + } +} diff --git a/src/main/java/track/messenger/net/Session.java b/src/main/java/track/messenger/net/Session.java new file mode 100644 index 00000000..9d3d18e1 --- /dev/null +++ b/src/main/java/track/messenger/net/Session.java @@ -0,0 +1,67 @@ +package track.messenger.net; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import track.messenger.User; +import track.messenger.messages.Message; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +/** + * Сессия связывает бизнес-логику и сетевую часть. + * Бизнес логика представлена объектом юзера - владельца сессии. + * Сетевая часть привязывает нас к определнному соединению по сети (от клиента) + */ +public class Session { + + static Logger LOG = LoggerFactory.getLogger(Session.class); + + /** + * Пользователь сессии, пока не прошел логин, user == null + * После логина устанавливается реальный пользователь + */ + private User user; + + // сокет на клиента + private Socket socket; + + private Protocol protocol; + /** + * С каждым сокетом связано 2 канала in/out + */ + private InputStream in; + private OutputStream out; + + public Session(Socket socket, Protocol protocol) { + this.socket = socket; + this.protocol = protocol; + try { + in = socket.getInputStream(); + out = socket.getOutputStream(); + } catch (IOException e) { + LOG.error(e.getMessage()); + } + } + + public void send(Message msg) throws ProtocolException, IOException { + out.write(protocol.encode(msg)); + } + + public Message readMessage() throws ProtocolException { + return protocol.decode(in); + } + + public void close() { + // TODO: закрыть in/out каналы и сокет. Освободить другие ресурсы, если необходимо + try { + in.close(); + out.close(); + socket.close(); + } catch (IOException ex) { + LOG.error(ex.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/track/messenger/store/MessageStore.java b/src/main/java/track/messenger/store/MessageStore.java new file mode 100644 index 00000000..1001ad1b --- /dev/null +++ b/src/main/java/track/messenger/store/MessageStore.java @@ -0,0 +1,38 @@ +package track.messenger.store; + +import track.messenger.messages.Message; + +import java.util.List; + +public interface MessageStore { + /** + * получаем список ид пользователей заданного чата + */ + List getChatsByUserId(Long userId); + + /** + * получить информацию о чате + */ + //Chat getChatById(Long chatId); + + /** + * Список сообщений из чата + */ + List getMessagesFromChat(Long chatId); + + /** + * Получить информацию о сообщении + */ + Message getMessageById(Long messageId); + + /** + * Добавить сообщение в чат + */ + void addMessage(Long chatId, Message message); + + /** + * Добавить пользователя к чату + */ + void addUserToChat(Long userId, Long chatId); + +} diff --git a/src/main/java/track/messenger/store/UserStore.java b/src/main/java/track/messenger/store/UserStore.java new file mode 100644 index 00000000..174dfcfc --- /dev/null +++ b/src/main/java/track/messenger/store/UserStore.java @@ -0,0 +1,30 @@ +package track.messenger.store; + +import track.messenger.User; + +public interface UserStore { + /** + * Добавить пользователя в хранилище + * Вернуть его же + */ + User addUser(User user); + + /** + * Обновить информацию о пользователе + */ + User updateUser(User user); + + /** + * + * Получить пользователя по логину/паролю + * return null if user not found + */ + User getUser(String login, String pass); + + /** + * + * Получить пользователя по id, например запрос информации/профиля + * return null if user not found + */ + User getUserById(Long id); +} diff --git a/src/main/java/track/messenger/teacher/client/MessengerClient.java b/src/main/java/track/messenger/teacher/client/MessengerClient.java new file mode 100644 index 00000000..26cace9d --- /dev/null +++ b/src/main/java/track/messenger/teacher/client/MessengerClient.java @@ -0,0 +1,183 @@ +package track.messenger.teacher.client; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import track.messenger.User; +import track.messenger.messages.Message; +import track.messenger.messages.TextMessage; +import track.messenger.net.BinaryProtocol; +import track.messenger.net.Protocol; +import track.messenger.net.ProtocolException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.Arrays; +import java.util.Scanner; + + +/** + * + */ +public class MessengerClient { + + + /** + * Механизм логирования позволяет более гибко управлять записью данных в лог (консоль, файл и тд) + */ + static Logger LOG = LoggerFactory.getLogger(MessengerClient.class); + + /** + * Протокол, хост и порт инициализируются из конфига + */ + private Protocol protocol; + private int port; + private String host; + private User currentUser = null; + + /** + * С каждым сокетом связано 2 канала in/out + */ + private InputStream in; + private OutputStream out; + + public Protocol getProtocol() { + return protocol; + } + + public void setProtocol(Protocol protocol) { + this.protocol = protocol; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public void initSocket() throws IOException { + Socket socket = new Socket(host, port); + in = socket.getInputStream(); + out = socket.getOutputStream(); + + /* + Тред "слушает" сокет на наличие входящих сообщений от сервера + */ + Thread socketListenerThread = new Thread(() -> { + LOG.info("Starting listener thread..."); + while (!Thread.currentThread().isInterrupted()) { + try { + // Здесь поток блокируется на ожидании данных + Message msg = protocol.decode(in); + if (msg != null) { + onMessage(msg); + } + } catch (Exception e) { + LOG.error("Failed to process connection: {}", e); + e.printStackTrace(); + Thread.currentThread().interrupt(); + } + } + }); + + socketListenerThread.start(); + } + + /** + * Реагируем на входящее сообщение + */ + public void onMessage(Message msg) { + LOG.info("Message received: {}", msg); + } + + /** + * Обрабатывает входящую строку, полученную с консоли + * Формат строки можно посмотреть в вики проекта + */ + public void processInput(String line) throws IOException, ProtocolException { + String[] tokens = line.split(" "); + LOG.info("Tokens: {}", Arrays.toString(tokens)); + String cmdType = tokens[0]; + switch (cmdType) { + case "/login": + // TODO: реализация + break; + case "/help": + // TODO: реализация + break; + case "/text": + // FIXME: пример реализации для простого текстового сообщения + TextMessage sendMessage = new TextMessage(currentUser.getId()); + sendMessage.setText(tokens[1]); + send(sendMessage); + break; + // TODO: implement another types from wiki + + default: + LOG.error("Invalid input: " + line); + } + } + + /** + * Отправка сообщения в сокет клиент -> сервер + */ + public void send(Message msg) throws IOException, ProtocolException { + LOG.info(msg.toString()); + out.write(protocol.encode(msg)); + out.flush(); // принудительно проталкиваем буфер с данными + } + + public void close() { + try { + in.close(); + out.close(); + } catch (IOException ex) { + LOG.error(ex.getMessage()); + } + } + + public static void main(String[] args) throws Exception { + + MessengerClient client = new MessengerClient(); + client.setHost("localhost"); + client.setPort(4242); + client.setProtocol(new BinaryProtocol()); + + try { + client.initSocket(); + + // Цикл чтения с консоли + Scanner scanner = new Scanner(System.in); + System.out.println("$"); + while (true) { + String input = scanner.nextLine(); + if ("q".equals(input)) { + return; + } + try { + client.processInput(input); + } catch (ProtocolException | IOException e) { + LOG.error("Failed to process user input", e); + } + } + } catch (Exception e) { + LOG.error("Application failed.", e); + } finally { + if (client != null) { + // TODO + client.close(); + } + } + } +} \ No newline at end of file From c3d1a5de50504ec1a2475b7c2d79abd59505a386 Mon Sep 17 00:00:00 2001 From: IlRomanenko Date: Thu, 20 Apr 2017 02:28:54 +0300 Subject: [PATCH 3/3] Implemented simple messenger It works, but has some features. --- pom.xml | 15 + src/main/java/track/messenger/Chat.java | 10 - .../track/messenger/MessageServerMain.java | 16 +- src/main/java/track/messenger/User.java | 36 -- .../messenger/commands/ChatCreateCommand.java | 20 ++ .../commands/ChatHistoryCommand.java | 23 ++ .../messenger/commands/ChatListCommand.java | 26 ++ .../track/messenger/commands/Command.java | 1 + .../messenger/commands/CommandFactory.java | 37 +- .../track/messenger/commands/InfoCommand.java | 21 ++ .../messenger/commands/LoginCommand.java | 35 +- .../commands/TextReceiveCommand.java | 38 +++ .../messenger/commands/TextSendCommand.java | 56 +++ .../messenger/commands/UserCreateCommand.java | 23 ++ .../messenger/commands/UserListCommand.java | 15 +- .../messenger/messages/BaseTextMessage.java | 21 ++ .../messenger/messages/LoginMessage.java | 21 -- .../track/messenger/messages/Message.java | 17 +- .../java/track/messenger/messages/Type.java | 2 +- .../messages/requests/ChatCreateMessage.java | 32 ++ .../messages/requests/ChatHistoryMessage.java | 24 ++ .../messages/requests/ChatListMessage.java | 17 + .../messages/requests/ChatMessage.java | 30 ++ .../messages/requests/InfoMessage.java | 24 ++ .../messages/requests/LoginMessage.java | 30 ++ .../messages/requests/UserCreateMessage.java | 17 + .../messages/requests/UserListMessage.java | 17 + .../responses/ChatHistoryResultMessage.java | 25 ++ .../responses/ChatListResultMessage.java | 28 ++ .../messages/responses/InfoResultMessage.java | 24 ++ .../messages/responses/StatusMessage.java | 38 +++ .../messages/{ => responses}/TextMessage.java | 26 +- .../responses/UserListResultMessage.java | 26 ++ .../java/track/messenger/models/Chat.java | 52 +++ .../java/track/messenger/models/User.java | 67 ++++ .../track/messenger/net/BinaryProtocol.java | 80 ++++- .../track/messenger/net/MessengerServer.java | 81 +++-- .../java/track/messenger/net/Protocol.java | 11 +- .../java/track/messenger/net/Session.java | 117 +++++-- .../track/messenger/net/SessionException.java | 15 + .../track/messenger/net/SessionsHandler.java | 134 ++++++++ .../store/DatabaseConfiguration.java | 32 ++ .../messenger/store/DatabaseMessageStore.java | 322 ++++++++++++++++++ .../messenger/store/DatabaseUserStore.java | 181 ++++++++++ .../track/messenger/store/MessageStore.java | 17 +- .../track/messenger/store/MessengerDAO.java | 61 ++++ .../java/track/messenger/store/UserStore.java | 8 +- .../teacher/client/MessengerClient.java | 232 +++++++++++-- .../spring/server/SpringContainerExample.java | 6 +- src/main/resources/app.properties | 7 +- src/main/resources/spring-config.xml | 10 +- 51 files changed, 1978 insertions(+), 246 deletions(-) delete mode 100644 src/main/java/track/messenger/Chat.java delete mode 100644 src/main/java/track/messenger/User.java create mode 100644 src/main/java/track/messenger/commands/TextReceiveCommand.java create mode 100644 src/main/java/track/messenger/commands/TextSendCommand.java create mode 100644 src/main/java/track/messenger/messages/BaseTextMessage.java delete mode 100644 src/main/java/track/messenger/messages/LoginMessage.java create mode 100644 src/main/java/track/messenger/messages/requests/ChatCreateMessage.java create mode 100644 src/main/java/track/messenger/messages/requests/ChatHistoryMessage.java create mode 100644 src/main/java/track/messenger/messages/requests/ChatListMessage.java create mode 100644 src/main/java/track/messenger/messages/requests/ChatMessage.java create mode 100644 src/main/java/track/messenger/messages/requests/InfoMessage.java create mode 100644 src/main/java/track/messenger/messages/requests/LoginMessage.java create mode 100644 src/main/java/track/messenger/messages/requests/UserCreateMessage.java create mode 100644 src/main/java/track/messenger/messages/requests/UserListMessage.java create mode 100644 src/main/java/track/messenger/messages/responses/ChatHistoryResultMessage.java create mode 100644 src/main/java/track/messenger/messages/responses/ChatListResultMessage.java create mode 100644 src/main/java/track/messenger/messages/responses/InfoResultMessage.java create mode 100644 src/main/java/track/messenger/messages/responses/StatusMessage.java rename src/main/java/track/messenger/messages/{ => responses}/TextMessage.java (59%) create mode 100644 src/main/java/track/messenger/messages/responses/UserListResultMessage.java create mode 100644 src/main/java/track/messenger/models/Chat.java create mode 100644 src/main/java/track/messenger/models/User.java create mode 100644 src/main/java/track/messenger/net/SessionException.java create mode 100644 src/main/java/track/messenger/net/SessionsHandler.java create mode 100644 src/main/java/track/messenger/store/DatabaseConfiguration.java create mode 100644 src/main/java/track/messenger/store/DatabaseMessageStore.java create mode 100644 src/main/java/track/messenger/store/DatabaseUserStore.java create mode 100644 src/main/java/track/messenger/store/MessengerDAO.java diff --git a/pom.xml b/pom.xml index e3bf7f9b..9acedf9f 100755 --- a/pom.xml +++ b/pom.xml @@ -231,6 +231,21 @@ slf4j-log4j12 1.7.5 + + com.zaxxer + HikariCP + 2.5.1 + + + com.h2database + h2 + 1.4.193 + + + org.springframework + spring-jdbc + 4.3.4.RELEASE + org.hibernate diff --git a/src/main/java/track/messenger/Chat.java b/src/main/java/track/messenger/Chat.java deleted file mode 100644 index 1fddf22e..00000000 --- a/src/main/java/track/messenger/Chat.java +++ /dev/null @@ -1,10 +0,0 @@ -package track.messenger; - -/** - * Tehnotrack - * track.messenger - *

- * Created by ilya on 11.04.17. - */ -public class Chat { -} diff --git a/src/main/java/track/messenger/MessageServerMain.java b/src/main/java/track/messenger/MessageServerMain.java index 1f68fff0..1fc069d3 100644 --- a/src/main/java/track/messenger/MessageServerMain.java +++ b/src/main/java/track/messenger/MessageServerMain.java @@ -1,6 +1,7 @@ package track.messenger; -import track.messenger.net.BinaryProtocol; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; import track.messenger.net.MessengerServer; /** @@ -10,16 +11,9 @@ public class MessageServerMain { public static void main(String[] args) { - if (args.length != 1) { - System.out.println("Usage "); - return; - } - - int port = Integer.parseInt(args[0]); - - MessengerServer server = new MessengerServer(2, new BinaryProtocol()); - - server.start(port); + ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); + MessengerServer server = (MessengerServer)context.getBean("messengerServer"); + server.start(); } } diff --git a/src/main/java/track/messenger/User.java b/src/main/java/track/messenger/User.java deleted file mode 100644 index e7e9ef8c..00000000 --- a/src/main/java/track/messenger/User.java +++ /dev/null @@ -1,36 +0,0 @@ -package track.messenger; - -import java.util.List; - -/** - * Tehnotrack - * track.messenger - *

- * Created by ilya on 11.04.17. - */ -public class User { - private long id; - private String nickname; - private List chatIds; - - public User(long id, String nickname) { - this.id = id; - this.nickname = nickname; - } - - public List getChatIds() { - return chatIds; - } - - public void setChatIds(List chatIds) { - this.chatIds = chatIds; - } - - public long getId() { - return id; - } - - public String getNickname() { - return nickname; - } -} diff --git a/src/main/java/track/messenger/commands/ChatCreateCommand.java b/src/main/java/track/messenger/commands/ChatCreateCommand.java index 5ed80ac7..cb1e813b 100644 --- a/src/main/java/track/messenger/commands/ChatCreateCommand.java +++ b/src/main/java/track/messenger/commands/ChatCreateCommand.java @@ -1,7 +1,13 @@ package track.messenger.commands; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import track.messenger.messages.Message; +import track.messenger.messages.requests.ChatCreateMessage; +import track.messenger.messages.responses.StatusMessage; +import track.messenger.models.Chat; import track.messenger.net.Session; +import track.messenger.store.MessageStore; /** * Tehnotrack @@ -9,9 +15,23 @@ *

* Created by ilya on 11.04.17. */ +@Service public class ChatCreateCommand implements Command { + + @Autowired + private MessageStore messageStore; + @Override public void execute(Session session, Message message) { + ChatCreateMessage msg = (ChatCreateMessage) message; + + Chat chat = messageStore.createChat(msg.getSenderId(), msg.getUsers(), msg.getChatName()); + + if (chat == null) { + session.send(new StatusMessage(StatusMessage.Status.FAIL)); + } else { + session.send(new StatusMessage(StatusMessage.Status.OK)); + } } } diff --git a/src/main/java/track/messenger/commands/ChatHistoryCommand.java b/src/main/java/track/messenger/commands/ChatHistoryCommand.java index 6654326c..364dc798 100644 --- a/src/main/java/track/messenger/commands/ChatHistoryCommand.java +++ b/src/main/java/track/messenger/commands/ChatHistoryCommand.java @@ -1,7 +1,15 @@ package track.messenger.commands; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import track.messenger.messages.Message; +import track.messenger.messages.requests.ChatHistoryMessage; +import track.messenger.messages.responses.ChatHistoryResultMessage; +import track.messenger.messages.responses.StatusMessage; import track.messenger.net.Session; +import track.messenger.store.MessageStore; + +import java.util.List; /** * Tehnotrack @@ -9,9 +17,24 @@ *

* Created by ilya on 11.04.17. */ +@Service public class ChatHistoryCommand implements Command { + + @Autowired + private MessageStore messageStore; + @Override public void execute(Session session, Message message) { + ChatHistoryMessage msg = (ChatHistoryMessage) message; + + List messagesIds = messageStore.getMessagesFromChat(msg.getChatId()); + + if (messagesIds == null) { + session.send(new StatusMessage(StatusMessage.Status.NOT_FOUND)); + } else { + session.send(new ChatHistoryResultMessage(messagesIds)); + } + } } diff --git a/src/main/java/track/messenger/commands/ChatListCommand.java b/src/main/java/track/messenger/commands/ChatListCommand.java index 26c5f2ce..06bfc5eb 100644 --- a/src/main/java/track/messenger/commands/ChatListCommand.java +++ b/src/main/java/track/messenger/commands/ChatListCommand.java @@ -1,7 +1,16 @@ package track.messenger.commands; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import track.messenger.messages.Message; +import track.messenger.messages.requests.ChatListMessage; +import track.messenger.messages.responses.ChatListResultMessage; +import track.messenger.messages.responses.StatusMessage; import track.messenger.net.Session; +import track.messenger.store.MessageStore; + +import java.util.List; +import java.util.Map; /** * Tehnotrack @@ -9,11 +18,28 @@ *

* Created by ilya on 11.04.17. */ +@Service public class ChatListCommand implements Command { + @Autowired + private MessageStore messageStore; @Override public void execute(Session session, Message message) { + ChatListMessage msg = (ChatListMessage)message; + + if (session.getUser() == null) { + session.send(new StatusMessage(StatusMessage.Status.FAIL)); + return; + } + + List> list = messageStore.getChatsByUserId(session.getUser().getId()); + + if (list == null) { + session.send(new StatusMessage(StatusMessage.Status.NOT_FOUND)); + } else { + session.send(new ChatListResultMessage(list)); + } } } diff --git a/src/main/java/track/messenger/commands/Command.java b/src/main/java/track/messenger/commands/Command.java index 75804a14..98d1afd5 100644 --- a/src/main/java/track/messenger/commands/Command.java +++ b/src/main/java/track/messenger/commands/Command.java @@ -12,4 +12,5 @@ public interface Command { void execute(Session session, Message message); + } diff --git a/src/main/java/track/messenger/commands/CommandFactory.java b/src/main/java/track/messenger/commands/CommandFactory.java index 5ea5ce69..017eb52f 100644 --- a/src/main/java/track/messenger/commands/CommandFactory.java +++ b/src/main/java/track/messenger/commands/CommandFactory.java @@ -1,5 +1,7 @@ package track.messenger.commands; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; import track.messenger.messages.Type; import java.util.Collections; @@ -12,29 +14,42 @@ *

* Created by ilya on 11.04.17. */ +@Repository public class CommandFactory { + private final Map commandMap; - private static Map commandMap; - - static { + @Autowired + public CommandFactory(LoginCommand loginCommand, TextReceiveCommand textReceiveCommand, InfoCommand infoCommand, + UserCreateCommand userCreateCommand, UserListCommand userListCommand, + ChatCreateCommand chatCreateCommand, ChatHistoryCommand chatHistoryCommand, + ChatListCommand chatListCommand, TextSendCommand textSendCommand) { Map tmpCommandMap = new HashMap<>(); + tmpCommandMap.put(Type.MSG_LOGIN, loginCommand); + tmpCommandMap.put(Type.MSG_INFO, infoCommand); + tmpCommandMap.put(Type.MSG_CREATE_USER, userCreateCommand); + tmpCommandMap.put(Type.MSG_USERS_LIST, userListCommand); - tmpCommandMap.put(Type.MSG_LOGIN, new LoginCommand()); - tmpCommandMap.put(Type.MSG_INFO, new InfoCommand()); + tmpCommandMap.put(Type.MSG_CHAT_CREATE, chatCreateCommand); + tmpCommandMap.put(Type.MSG_CHAT_HIST, chatHistoryCommand); + tmpCommandMap.put(Type.MSG_CHAT_LIST, chatListCommand); - tmpCommandMap.put(Type.MSG_CREATE_USER, new UserCreateCommand()); - tmpCommandMap.put(Type.MSG_USERS_LIST, new UserListCommand()); + tmpCommandMap.put(Type.MSG_TEXT, textReceiveCommand); - tmpCommandMap.put(Type.MSG_CHAT_CREATE, new ChatCreateCommand()); - tmpCommandMap.put(Type.MSG_CHAT_HIST, new ChatHistoryCommand()); - tmpCommandMap.put(Type.MSG_CHAT_LIST, new ChatListCommand()); + tmpCommandMap.put(Type.MSG_SEND_TEXT, textSendCommand); commandMap = Collections.unmodifiableMap(tmpCommandMap); + instance = this; } - public static Command create(Type type) { + public Command create(Type type) { return commandMap.getOrDefault(type, null); } + + private static CommandFactory instance; + + public static CommandFactory getInstance() { + return instance; + } } diff --git a/src/main/java/track/messenger/commands/InfoCommand.java b/src/main/java/track/messenger/commands/InfoCommand.java index 2db80dd2..e7207568 100644 --- a/src/main/java/track/messenger/commands/InfoCommand.java +++ b/src/main/java/track/messenger/commands/InfoCommand.java @@ -1,7 +1,14 @@ package track.messenger.commands; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import track.messenger.messages.Message; +import track.messenger.messages.requests.InfoMessage; +import track.messenger.messages.responses.InfoResultMessage; +import track.messenger.messages.responses.StatusMessage; +import track.messenger.models.User; import track.messenger.net.Session; +import track.messenger.store.UserStore; /** * Tehnotrack @@ -9,9 +16,23 @@ *

* Created by ilya on 11.04.17. */ +@Service public class InfoCommand implements Command { + + @Autowired + private UserStore userStore; + @Override public void execute(Session session, Message message) { + InfoMessage msg = (InfoMessage)message; + + User user = userStore.getUserById(msg.getUserId()); + + if (user == null) { + session.send(new StatusMessage(StatusMessage.Status.NOT_FOUND)); + } else { + session.send(new InfoResultMessage(user)); + } } } diff --git a/src/main/java/track/messenger/commands/LoginCommand.java b/src/main/java/track/messenger/commands/LoginCommand.java index c3f23040..11807b75 100644 --- a/src/main/java/track/messenger/commands/LoginCommand.java +++ b/src/main/java/track/messenger/commands/LoginCommand.java @@ -2,8 +2,16 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import track.messenger.messages.Message; +import track.messenger.messages.requests.LoginMessage; +import track.messenger.messages.responses.InfoResultMessage; +import track.messenger.messages.responses.StatusMessage; +import track.messenger.models.User; import track.messenger.net.Session; +import track.messenger.net.SessionException; +import track.messenger.store.UserStore; /** * Tehnotrack @@ -11,12 +19,35 @@ *

* Created by ilya on 11.04.17. */ +@Service public class LoginCommand implements Command { - private static Logger LOG = LoggerFactory.getLogger(LoginCommand.class); + private static Logger log = LoggerFactory.getLogger(LoginCommand.class); + + @Autowired + private UserStore userStore; @Override public void execute(Session session, Message message) { - LOG.info("Execute : ", message.toString()); + log.info("Execute login message : " + message.toString()); + + + LoginMessage loginMessage = (LoginMessage)message; + + User user = userStore.getUser(loginMessage.getLogin(), loginMessage.getPassword()); + + if (user == null) { + session.send(new StatusMessage(StatusMessage.Status.NOT_FOUND)); + return; + } + try { + session.setUser(user); + session.getHandler().authSession(session.getId(), user); + session.send(new InfoResultMessage(user)); + + } catch (SessionException ex) { + log.error(ex.getMessage()); + session.send(new StatusMessage(StatusMessage.Status.FAIL)); + } } } diff --git a/src/main/java/track/messenger/commands/TextReceiveCommand.java b/src/main/java/track/messenger/commands/TextReceiveCommand.java new file mode 100644 index 00000000..e13c3bab --- /dev/null +++ b/src/main/java/track/messenger/commands/TextReceiveCommand.java @@ -0,0 +1,38 @@ +package track.messenger.commands; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import track.messenger.messages.Message; +import track.messenger.messages.requests.ChatMessage; +import track.messenger.messages.responses.StatusMessage; +import track.messenger.messages.responses.TextMessage; +import track.messenger.net.Session; +import track.messenger.store.MessageStore; + +/** + * Tehnotrack + * track.messenger.commands + *

+ * Created by ilya on 14.04.17. + */ +@Service +public class TextReceiveCommand implements Command { + + @Autowired + private MessageStore messageStore; + + @Override + public void execute(Session session, Message message) { + ChatMessage msg = (ChatMessage)message; + + TextMessage storedMessage = messageStore.addMessage(msg.getChatId(), msg); + + if (storedMessage == null) { + session.send(new StatusMessage(StatusMessage.Status.FAIL)); + } else { + session.send(new StatusMessage(StatusMessage.Status.OK)); + + session.getHandler().sendMessageInChat(session, storedMessage); + } + } +} diff --git a/src/main/java/track/messenger/commands/TextSendCommand.java b/src/main/java/track/messenger/commands/TextSendCommand.java new file mode 100644 index 00000000..028383ab --- /dev/null +++ b/src/main/java/track/messenger/commands/TextSendCommand.java @@ -0,0 +1,56 @@ +package track.messenger.commands; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import track.messenger.messages.Message; +import track.messenger.messages.responses.TextMessage; +import track.messenger.models.Chat; +import track.messenger.models.User; +import track.messenger.net.Session; +import track.messenger.store.MessageStore; + +import java.util.Map; + +/** + * Tehnotrack + * track.messenger.commands + *

+ * Created by ilya on 19.04.17. + */ +@Service +public class TextSendCommand implements Command { + + private Logger log = LoggerFactory.getLogger(TextSendCommand.class); + + + @Autowired + private MessageStore messageStore; + + @Override + public void execute(Session session, Message message) { + TextMessage msg = (TextMessage) message; + + Chat chat = messageStore.getChatById(msg.getChatId()); + + if (chat == null) { + log.error("Sending message to unknown chat"); + return; + } + + Map authSessions = session.getHandler().getAuthSessions(); + + for (User user : chat.getUsers()) { + Session userSession = authSessions.getOrDefault(user, null); + if (userSession == null) { + continue; + } + if (user.getId() == session.getUser().getId()) { + continue; + } + userSession.send(msg); + } + + } +} diff --git a/src/main/java/track/messenger/commands/UserCreateCommand.java b/src/main/java/track/messenger/commands/UserCreateCommand.java index 5dc5a907..f4bd0441 100644 --- a/src/main/java/track/messenger/commands/UserCreateCommand.java +++ b/src/main/java/track/messenger/commands/UserCreateCommand.java @@ -1,7 +1,15 @@ package track.messenger.commands; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import track.messenger.messages.Message; +import track.messenger.messages.requests.UserCreateMessage; +import track.messenger.messages.responses.StatusMessage; +import track.messenger.models.User; import track.messenger.net.Session; +import track.messenger.store.UserStore; /** * Tehnotrack @@ -9,12 +17,27 @@ *

* Created by ilya on 11.04.17. */ +@Service public class UserCreateCommand implements Command { + private static Logger log = LoggerFactory.getLogger(UserCreateCommand.class); + @Autowired + private UserStore userStore; @Override public void execute(Session session, Message message) { + UserCreateMessage msg = (UserCreateMessage) message; + if (msg == null) { + session.send(new StatusMessage(StatusMessage.Status.FAIL)); + return; + } + User user = userStore.addUser(msg.getLogin(), msg.getPassword()); + if (user == null) { + session.send(new StatusMessage(StatusMessage.Status.FAIL)); + } else { + session.send(new StatusMessage(StatusMessage.Status.OK)); + } } } diff --git a/src/main/java/track/messenger/commands/UserListCommand.java b/src/main/java/track/messenger/commands/UserListCommand.java index 86f317f3..05cc484b 100644 --- a/src/main/java/track/messenger/commands/UserListCommand.java +++ b/src/main/java/track/messenger/commands/UserListCommand.java @@ -1,7 +1,14 @@ package track.messenger.commands; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import track.messenger.messages.Message; +import track.messenger.messages.responses.UserListResultMessage; +import track.messenger.models.User; import track.messenger.net.Session; +import track.messenger.store.UserStore; + +import java.util.List; /** * Tehnotrack @@ -9,9 +16,15 @@ *

* Created by ilya on 11.04.17. */ +@Service public class UserListCommand implements Command { + + @Autowired + private UserStore userStore; + @Override public void execute(Session session, Message message) { - + List users = userStore.getUsers(); + session.send(new UserListResultMessage(users)); } } diff --git a/src/main/java/track/messenger/messages/BaseTextMessage.java b/src/main/java/track/messenger/messages/BaseTextMessage.java new file mode 100644 index 00000000..76e0959a --- /dev/null +++ b/src/main/java/track/messenger/messages/BaseTextMessage.java @@ -0,0 +1,21 @@ +package track.messenger.messages; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 19.04.17. + */ +public class BaseTextMessage extends Message { + + protected String text; + + public BaseTextMessage(Long senderId, String text) { + super(senderId, Type.MSG_TEXT); + this.text = text; + } + + public String getText() { + return text; + } +} diff --git a/src/main/java/track/messenger/messages/LoginMessage.java b/src/main/java/track/messenger/messages/LoginMessage.java deleted file mode 100644 index cecfef50..00000000 --- a/src/main/java/track/messenger/messages/LoginMessage.java +++ /dev/null @@ -1,21 +0,0 @@ -package track.messenger.messages; - -/** - * Tehnotrack - * track.messenger.messages - *

- * Created by ilya on 11.04.17. - */ -public class LoginMessage extends Message{ - - private String login; - private String password; - - public LoginMessage(Long senderId, String login, String password) { - super(senderId, Type.MSG_LOGIN); - this.login = login; - this.password = password; - } - - -} diff --git a/src/main/java/track/messenger/messages/Message.java b/src/main/java/track/messenger/messages/Message.java index e769044e..d953a145 100644 --- a/src/main/java/track/messenger/messages/Message.java +++ b/src/main/java/track/messenger/messages/Message.java @@ -7,26 +7,14 @@ */ public abstract class Message implements Serializable { - private static volatile long ID_COUNTER = 0; - - private Long id; private Long senderId; private Type type; public Message(Long senderId, Type type) { - id = ID_COUNTER++; this.senderId = senderId; this.type = type; } - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - public Long getSenderId() { return senderId; } @@ -42,4 +30,9 @@ public Type getType() { public void setType(Type type) { this.type = type; } + + @Override + public String toString() { + return String.format("Message{sender: %d type: %s}", senderId, type.toString()); + } } diff --git a/src/main/java/track/messenger/messages/Type.java b/src/main/java/track/messenger/messages/Type.java index e6bbdfcb..0664657d 100644 --- a/src/main/java/track/messenger/messages/Type.java +++ b/src/main/java/track/messenger/messages/Type.java @@ -19,5 +19,5 @@ public enum Type { MSG_CHAT_LIST_RESULT, MSG_CHAT_HIST_RESULT, MSG_INFO_RESULT, - MSG_USERS_LIST_RESULT + MSG_SEND_TEXT, MSG_USERS_LIST_RESULT } diff --git a/src/main/java/track/messenger/messages/requests/ChatCreateMessage.java b/src/main/java/track/messenger/messages/requests/ChatCreateMessage.java new file mode 100644 index 00000000..92385a81 --- /dev/null +++ b/src/main/java/track/messenger/messages/requests/ChatCreateMessage.java @@ -0,0 +1,32 @@ +package track.messenger.messages.requests; + +import track.messenger.messages.Message; +import track.messenger.messages.Type; + +import java.util.List; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 15.04.17. + */ +public class ChatCreateMessage extends Message { + + private String chatName; + private List users; + + public ChatCreateMessage(Long senderId, String chatName, List users) { + super(senderId, Type.MSG_CHAT_CREATE); + this.chatName = chatName; + this.users = users; + } + + public String getChatName() { + return chatName; + } + + public List getUsers() { + return users; + } +} diff --git a/src/main/java/track/messenger/messages/requests/ChatHistoryMessage.java b/src/main/java/track/messenger/messages/requests/ChatHistoryMessage.java new file mode 100644 index 00000000..69a057be --- /dev/null +++ b/src/main/java/track/messenger/messages/requests/ChatHistoryMessage.java @@ -0,0 +1,24 @@ +package track.messenger.messages.requests; + +import track.messenger.messages.Message; +import track.messenger.messages.Type; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 15.04.17. + */ +public class ChatHistoryMessage extends Message { + + private Long chatId; + + public ChatHistoryMessage(Long senderId, Long chatId) { + super(senderId, Type.MSG_CHAT_HIST); + this.chatId = chatId; + } + + public Long getChatId() { + return chatId; + } +} diff --git a/src/main/java/track/messenger/messages/requests/ChatListMessage.java b/src/main/java/track/messenger/messages/requests/ChatListMessage.java new file mode 100644 index 00000000..e7424590 --- /dev/null +++ b/src/main/java/track/messenger/messages/requests/ChatListMessage.java @@ -0,0 +1,17 @@ +package track.messenger.messages.requests; + +import track.messenger.messages.Message; +import track.messenger.messages.Type; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 15.04.17. + */ +public class ChatListMessage extends Message { + + public ChatListMessage(Long senderId) { + super(senderId, Type.MSG_CHAT_LIST); + } +} diff --git a/src/main/java/track/messenger/messages/requests/ChatMessage.java b/src/main/java/track/messenger/messages/requests/ChatMessage.java new file mode 100644 index 00000000..19eb6860 --- /dev/null +++ b/src/main/java/track/messenger/messages/requests/ChatMessage.java @@ -0,0 +1,30 @@ +package track.messenger.messages.requests; + +import track.messenger.messages.BaseTextMessage; + +/** + * Tehnotrack + * track.messenger.messages.requests + *

+ * Created by ilya on 19.04.17. + */ +public class ChatMessage extends BaseTextMessage { + + private long chatId; + + public ChatMessage(Long senderId, Long chatId, String text) { + super(senderId, text); + this.chatId = chatId; + } + + public long getChatId() { + return chatId; + } + + @Override + public String toString() { + return "TextMessage{" + + "text='" + text + '\'' + + '}'; + } +} diff --git a/src/main/java/track/messenger/messages/requests/InfoMessage.java b/src/main/java/track/messenger/messages/requests/InfoMessage.java new file mode 100644 index 00000000..e919f119 --- /dev/null +++ b/src/main/java/track/messenger/messages/requests/InfoMessage.java @@ -0,0 +1,24 @@ +package track.messenger.messages.requests; + +import track.messenger.messages.Message; +import track.messenger.messages.Type; + +/** + * Tehnotrack + * track.messenger.messages.requests + *

+ * Created by ilya on 15.04.17. + */ +public class InfoMessage extends Message { + + private Long userId; + + public InfoMessage(Long userId) { + super(0L, Type.MSG_INFO); + this.userId = userId; + } + + public Long getUserId() { + return userId; + } +} diff --git a/src/main/java/track/messenger/messages/requests/LoginMessage.java b/src/main/java/track/messenger/messages/requests/LoginMessage.java new file mode 100644 index 00000000..16c8caba --- /dev/null +++ b/src/main/java/track/messenger/messages/requests/LoginMessage.java @@ -0,0 +1,30 @@ +package track.messenger.messages.requests; + +import track.messenger.messages.Message; +import track.messenger.messages.Type; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 11.04.17. + */ +public class LoginMessage extends Message { + + private String login; + private String password; + + public LoginMessage(String login, String password) { + super(0L, Type.MSG_LOGIN); + this.login = login; + this.password = password; + } + + public String getLogin() { + return login; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/track/messenger/messages/requests/UserCreateMessage.java b/src/main/java/track/messenger/messages/requests/UserCreateMessage.java new file mode 100644 index 00000000..02c19c82 --- /dev/null +++ b/src/main/java/track/messenger/messages/requests/UserCreateMessage.java @@ -0,0 +1,17 @@ +package track.messenger.messages.requests; + +import track.messenger.messages.Type; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 15.04.17. + */ +public class UserCreateMessage extends LoginMessage { + + public UserCreateMessage(String login, String password) { + super(login, password); + setType(Type.MSG_CREATE_USER); + } +} diff --git a/src/main/java/track/messenger/messages/requests/UserListMessage.java b/src/main/java/track/messenger/messages/requests/UserListMessage.java new file mode 100644 index 00000000..53ea22d3 --- /dev/null +++ b/src/main/java/track/messenger/messages/requests/UserListMessage.java @@ -0,0 +1,17 @@ +package track.messenger.messages.requests; + +import track.messenger.messages.Message; +import track.messenger.messages.Type; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 15.04.17. + */ +public class UserListMessage extends Message { + + public UserListMessage() { + super(0L, Type.MSG_USERS_LIST); + } +} diff --git a/src/main/java/track/messenger/messages/responses/ChatHistoryResultMessage.java b/src/main/java/track/messenger/messages/responses/ChatHistoryResultMessage.java new file mode 100644 index 00000000..c446e97e --- /dev/null +++ b/src/main/java/track/messenger/messages/responses/ChatHistoryResultMessage.java @@ -0,0 +1,25 @@ +package track.messenger.messages.responses; + +import track.messenger.messages.Type; + +import java.util.List; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 15.04.17. + */ +public class ChatHistoryResultMessage extends StatusMessage { + + private List messagesId; + + public ChatHistoryResultMessage(List messagesId) { + super(Status.OK, Type.MSG_CHAT_HIST_RESULT); + this.messagesId = messagesId; + } + + public List getMessagesId() { + return messagesId; + } +} diff --git a/src/main/java/track/messenger/messages/responses/ChatListResultMessage.java b/src/main/java/track/messenger/messages/responses/ChatListResultMessage.java new file mode 100644 index 00000000..93e19b14 --- /dev/null +++ b/src/main/java/track/messenger/messages/responses/ChatListResultMessage.java @@ -0,0 +1,28 @@ +package track.messenger.messages.responses; + +import track.messenger.messages.Type; + +import java.util.List; +import java.util.Map; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 15.04.17. + */ +public class ChatListResultMessage extends StatusMessage { + + private List> chats; + + public ChatListResultMessage(List> chats) { + super(Status.OK, Type.MSG_CHAT_LIST_RESULT); + this.chats = chats; + + } + + public List> getChats() { + return chats; + } + +} diff --git a/src/main/java/track/messenger/messages/responses/InfoResultMessage.java b/src/main/java/track/messenger/messages/responses/InfoResultMessage.java new file mode 100644 index 00000000..36f60ec6 --- /dev/null +++ b/src/main/java/track/messenger/messages/responses/InfoResultMessage.java @@ -0,0 +1,24 @@ +package track.messenger.messages.responses; + +import track.messenger.messages.Type; +import track.messenger.models.User; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 13.04.17. + */ +public class InfoResultMessage extends StatusMessage{ + + private User user; + + public InfoResultMessage(User user) { + super(Status.OK, Type.MSG_INFO_RESULT); + this.user = user; + } + + public User getUser() { + return user; + } +} diff --git a/src/main/java/track/messenger/messages/responses/StatusMessage.java b/src/main/java/track/messenger/messages/responses/StatusMessage.java new file mode 100644 index 00000000..b3b8ef40 --- /dev/null +++ b/src/main/java/track/messenger/messages/responses/StatusMessage.java @@ -0,0 +1,38 @@ +package track.messenger.messages.responses; + +import track.messenger.messages.Message; +import track.messenger.messages.Type; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 13.04.17. + */ +public class StatusMessage extends Message { + + public enum Status { + OK, NOT_FOUND, FAIL; + } + + private Status status; + + public StatusMessage(Status status) { + super(-1L, Type.MSG_STATUS); + this.status = status; + } + + public StatusMessage(Status status, Type type) { + super(-1L, type); + this.status = status; + } + + public Status getStatus() { + return status; + } + + @Override + public String toString() { + return "StatusMessage{ " + status.toString() + " }"; + } +} diff --git a/src/main/java/track/messenger/messages/TextMessage.java b/src/main/java/track/messenger/messages/responses/TextMessage.java similarity index 59% rename from src/main/java/track/messenger/messages/TextMessage.java rename to src/main/java/track/messenger/messages/responses/TextMessage.java index b804f2ed..4a336661 100644 --- a/src/main/java/track/messenger/messages/TextMessage.java +++ b/src/main/java/track/messenger/messages/responses/TextMessage.java @@ -1,23 +1,31 @@ -package track.messenger.messages; +package track.messenger.messages.responses; + +import track.messenger.messages.BaseTextMessage; +import track.messenger.messages.Type; import java.util.Objects; /** * Простое текстовое сообщение */ -public class TextMessage extends Message { - private String text; +public class TextMessage extends BaseTextMessage{ + + private long id; + private long chatId; - public TextMessage(Long senderId) { - super(senderId, Type.MSG_TEXT); + public TextMessage(long id, Long senderId, Long chatId, String text) { + super(senderId, text); + setType(Type.MSG_SEND_TEXT); + this.id = id; + this.chatId = chatId; } - public String getText() { - return text; + public long getId() { + return id; } - public void setText(String text) { - this.text = text; + public long getChatId() { + return chatId; } @Override diff --git a/src/main/java/track/messenger/messages/responses/UserListResultMessage.java b/src/main/java/track/messenger/messages/responses/UserListResultMessage.java new file mode 100644 index 00000000..af87ed72 --- /dev/null +++ b/src/main/java/track/messenger/messages/responses/UserListResultMessage.java @@ -0,0 +1,26 @@ +package track.messenger.messages.responses; + +import track.messenger.messages.Type; +import track.messenger.models.User; + +import java.util.List; + +/** + * Tehnotrack + * track.messenger.messages + *

+ * Created by ilya on 15.04.17. + */ +public class UserListResultMessage extends StatusMessage { + + private List users; + + public UserListResultMessage(List users) { + super(Status.OK, Type.MSG_USERS_LIST_RESULT); + this.users = users; + } + + public List getUsers() { + return users; + } +} diff --git a/src/main/java/track/messenger/models/Chat.java b/src/main/java/track/messenger/models/Chat.java new file mode 100644 index 00000000..708faf59 --- /dev/null +++ b/src/main/java/track/messenger/models/Chat.java @@ -0,0 +1,52 @@ +package track.messenger.models; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Tehnotrack + * track.messenger + *

+ * Created by ilya on 11.04.17. + */ +public class Chat implements Serializable { + + private Long id; + private User admin; + private List users; + private String title; + + public Chat(Long id, String title, User admin, List users) { + this.id = id; + this.title = title; + this.admin = admin; + this.users = users; + } + + public Long getId() { + return id; + } + + public User getAdmin() { + return admin; + } + + public List getUsers() { + return users; + } + + public String getTitle() { + return title; + } + + public void setUsers(List users) { + this.users = users; + } + + @Override + public String toString() { + String ids = users.stream().map(user -> Long.toString(user.getId())).collect(Collectors.joining(" : ")); + return String.format("Chat{(id : %d) (title : %s) (User : %s) (Users : < %s >)}", id, title, admin.toString(), ids); + } +} diff --git a/src/main/java/track/messenger/models/User.java b/src/main/java/track/messenger/models/User.java new file mode 100644 index 00000000..2718ff24 --- /dev/null +++ b/src/main/java/track/messenger/models/User.java @@ -0,0 +1,67 @@ +package track.messenger.models; + +import java.io.Serializable; +import java.util.List; + +/** + * Tehnotrack + * track.messenger + *

+ * Created by ilya on 11.04.17. + */ +public class User implements Serializable { + + private long id; + private String nickname; + private String password; + + private List chatIds; + + public User(long id, String nickname, String password) { + this.id = id; + this.nickname = nickname; + this.password = password; + } + + public List getChatIds() { + return chatIds; + } + + public void setChatIds(List chatIds) { + this.chatIds = chatIds; + } + + public long getId() { + return id; + } + + public String getNickname() { + return nickname; + } + + @Override + public int hashCode() { + return ((Long)id).intValue(); + } + + @Override + public boolean equals(Object other) { + + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + return ((User)other).id == id; + } + + public String getPassword() { + return password; + } + + @Override + public String toString() { + return String.format("User{id: %d login: %s password %s}", id, nickname, password); + } +} diff --git a/src/main/java/track/messenger/net/BinaryProtocol.java b/src/main/java/track/messenger/net/BinaryProtocol.java index e718021b..775dd196 100644 --- a/src/main/java/track/messenger/net/BinaryProtocol.java +++ b/src/main/java/track/messenger/net/BinaryProtocol.java @@ -7,6 +7,8 @@ import java.io.IOException; import java.io.InputStream; +import java.io.ObjectInputStream; +import java.nio.ByteBuffer; /** * Tehnotrack @@ -16,26 +18,80 @@ */ public class BinaryProtocol implements Protocol { - private static Logger LOG = LoggerFactory.getLogger(BinaryProtocol.class); + private static Logger log = LoggerFactory.getLogger(BinaryProtocol.class); @Override public Message decode(InputStream stream) throws ProtocolException { - Message message = null; + Message message; try { - if (stream.available() > 0) { - message = SerializationUtils.deserialize(stream); - LOG.info("Decoded ", message); - } - } catch (IOException e) { - LOG.error(e.getMessage()); - throw new ProtocolException(e.getMessage()); + ObjectInputStream ois = new ObjectInputStream(stream); + message = (Message)ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + log.error(e.getMessage()); + throw new ProtocolException(e); } + log.info("Decoded ", message); return message; } @Override - public byte[] encode(Message msg) throws ProtocolException { - LOG.info("Encoded ", msg); - return SerializationUtils.serialize(msg); + public Integer decodeInteger(InputStream stream) { + byte[] buffer = new byte[Integer.BYTES]; + Integer result; + try { + if (stream.read(buffer) == Integer.BYTES) { + result = decodeInteger(buffer); + } else { + result = -1; + } + } catch (IOException ex) { + result = -1; + } + return result; + } + + @Override + public Integer decodeInteger(byte[] array) { + Integer result = 0; + Integer maxByteValue = 1 << Byte.SIZE; + for (int i = 0; i < Integer.BYTES; i++) { + result = (result << Byte.SIZE) + (array[i] + maxByteValue) % maxByteValue; + } + return result; + } + + @Override + public byte[] encode(Message msg) { + log.info("Encoded ", msg); + byte[] result = null; + try { + result = SerializationUtils.serialize(msg); + } catch (Exception ex) { + log.error(ex.getMessage()); + } + return result; + } + + @Override + public byte[] encode(Integer value) { + byte[] bytes = new byte[Integer.BYTES]; + for (int i = 0; i < Integer.BYTES; i++) { + bytes[Integer.BYTES - i - 1] = value.byteValue(); + value >>= Byte.SIZE; + } + return bytes; + } + + @Override + public byte[] serialize(Message msg) { + byte[] msgSer = encode(msg); + byte[] lenSer = encode(msgSer.length); + return ByteBuffer.allocate(msgSer.length + lenSer.length).put(lenSer).put(msgSer).array(); + } + + @Override + public Message deserialize(InputStream in) throws ProtocolException { + decodeInteger(in); + return decode(in); } } diff --git a/src/main/java/track/messenger/net/MessengerServer.java b/src/main/java/track/messenger/net/MessengerServer.java index 37c549c4..f7e6f517 100644 --- a/src/main/java/track/messenger/net/MessengerServer.java +++ b/src/main/java/track/messenger/net/MessengerServer.java @@ -2,82 +2,77 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import track.messenger.commands.Command; -import track.messenger.commands.CommandFactory; -import track.messenger.messages.Message; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; -import java.util.LinkedList; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.net.SocketException; /** * */ +@Component public class MessengerServer { - private static Logger LOG = LoggerFactory.getLogger(MessengerServer.class); + private static Logger log = LoggerFactory.getLogger(MessengerServer.class); - private Protocol protocol; private Thread acceptConnThread; + private ServerSocket acceptSocket; + private volatile SessionsHandler sessionsHandler; - private ExecutorService commandsPool; + @Value("${track.messenger.net.port:4242}") + private int port; - private volatile LinkedList sessions = new LinkedList<>(); + @Value("${track.messenger.net.threads:1}") + private int threadsCount; - private volatile boolean isAlive = true; + private Protocol protocol; - public MessengerServer(int threadsCount, Protocol protocol) { - this.protocol = protocol; - commandsPool = Executors.newFixedThreadPool(threadsCount); + @PostConstruct + public void postConstruct() { + this.protocol = new BinaryProtocol(); + sessionsHandler = new SessionsHandler(protocol, threadsCount); } - public void start(int port) { + public void start() { acceptConnThread = new Thread(() -> { - LOG.info(String.format("Start server on port %d", port)); + log.info(String.format("Start server on port %d", port)); try { acceptConnections(port); } catch (IOException ex) { - LOG.error(ex.getMessage()); - isAlive = false; + log.error(ex.getMessage()); } }); acceptConnThread.start(); - while (isAlive) { - if (sessions.isEmpty()) { - continue; - } - Session session = sessions.getFirst(); - try { - Message msg = session.readMessage(); - if (msg == null) { - continue; - } - Command cmd = CommandFactory.create(msg.getType()); - if (cmd == null) { - continue; - } - commandsPool.submit(() -> cmd.execute(session, msg)); - } catch (ProtocolException e) { - LOG.error(e.getMessage()); - } - sessions.push(session); + sessionsHandler.start(); + + log.info("Stop server"); + try { + acceptSocket.close(); + acceptConnThread.interrupt(); + } catch (IOException ex) { + log.error(ex.getMessage()); } - LOG.info("Stop server"); - acceptConnThread.stop(); } private void acceptConnections(int port) throws IOException { - ServerSocket serverSocket = new ServerSocket(port); + acceptSocket = new ServerSocket(port); while (true) { - Socket client = serverSocket.accept(); - LOG.info("Accept connection"); - sessions.add(new Session(client, protocol)); + try { + Socket clientSocket = acceptSocket.accept(); + log.info("Accept connection"); + sessionsHandler.createSession(clientSocket); + + } catch (SocketException ex) { + log.info("Accept server has stopped"); + break; + } } } diff --git a/src/main/java/track/messenger/net/Protocol.java b/src/main/java/track/messenger/net/Protocol.java index df1dafc0..b3266f6b 100644 --- a/src/main/java/track/messenger/net/Protocol.java +++ b/src/main/java/track/messenger/net/Protocol.java @@ -17,6 +17,15 @@ default Message decode(byte[] bytes) throws ProtocolException { Message decode(InputStream stream) throws ProtocolException; - byte[] encode(Message msg) throws ProtocolException; + Integer decodeInteger(InputStream stream); + Integer decodeInteger(byte[] array); + + byte[] encode(Message msg); + + byte[] encode(Integer integer); + + byte[] serialize(Message msg); + + Message deserialize(InputStream in) throws ProtocolException; } diff --git a/src/main/java/track/messenger/net/Session.java b/src/main/java/track/messenger/net/Session.java index 9d3d18e1..cfe16736 100644 --- a/src/main/java/track/messenger/net/Session.java +++ b/src/main/java/track/messenger/net/Session.java @@ -2,13 +2,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import track.messenger.User; import track.messenger.messages.Message; +import track.messenger.models.User; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; +import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; /** * Сессия связывает бизнес-логику и сетевую часть. @@ -17,51 +19,126 @@ */ public class Session { - static Logger LOG = LoggerFactory.getLogger(Session.class); + private static final int DEFAULT_CAPACITY = 1024; + private static Logger log = LoggerFactory.getLogger(Session.class); + private static volatile long SESSION_ID = 0; + private static final int SOCKET_TIMEOUT = 1; - /** - * Пользователь сессии, пока не прошел логин, user == null - * После логина устанавливается реальный пользователь - */ private User user; - - // сокет на клиента private Socket socket; + private SessionsHandler handler; + + private long id; + + private boolean alive = true; private Protocol protocol; - /** - * С каждым сокетом связано 2 канала in/out - */ + private InputStream in; private OutputStream out; - public Session(Socket socket, Protocol protocol) { + private ByteBuffer buffer; + private int remainsCount; + private boolean hasReadLength; + + public Session(Socket socket, Protocol protocol, SessionsHandler handler) { + this.id = SESSION_ID++; + this.socket = socket; + this.handler = handler; + this.protocol = protocol; try { + this.socket.setSoTimeout(SOCKET_TIMEOUT); in = socket.getInputStream(); out = socket.getOutputStream(); } catch (IOException e) { - LOG.error(e.getMessage()); + log.error(e.getMessage()); } + + buffer = ByteBuffer.allocate(DEFAULT_CAPACITY); + remainsCount = Integer.BYTES; + hasReadLength = false; + buffer.mark(); } - public void send(Message msg) throws ProtocolException, IOException { - out.write(protocol.encode(msg)); + public void send(Message msg) { + byte[] bytes = protocol.encode(msg); + try { + out.write(protocol.encode(bytes.length)); + out.write(bytes); + } catch (IOException ex) { + log.error(ex.getMessage()); + alive = false; + } } public Message readMessage() throws ProtocolException { - return protocol.decode(in); + Message msg = null; + try { + if (!hasReadLength) { + int currentRead = in.read(buffer.array(), buffer.position(), remainsCount); + if (currentRead == -1) { + alive = false; + return null; + } + remainsCount -= currentRead; + if (remainsCount != 0) { + return null; + } + + remainsCount = protocol.decodeInteger(buffer.array()); + buffer.reset(); + hasReadLength = true; + } + int currentRead = in.read(buffer.array(), buffer.position(), remainsCount); + + if (currentRead == -1) { + alive = false; + return null; + } + remainsCount -= currentRead; + if (remainsCount == 0) { + msg = protocol.decode(buffer.array()); + buffer.reset(); + hasReadLength = false; + remainsCount = Integer.BYTES; + } + } catch (SocketTimeoutException ex) { + msg = null; + } catch (IOException e) { + alive = false; + log.error(e.getMessage()); + } + return msg; + } + + public long getId() { + return id; } public void close() { - // TODO: закрыть in/out каналы и сокет. Освободить другие ресурсы, если необходимо try { - in.close(); - out.close(); + log.warn("......Close session!......"); socket.close(); } catch (IOException ex) { - LOG.error(ex.getMessage()); + log.error(ex.getMessage()); } } + + public boolean isAlive() { + return alive; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public SessionsHandler getHandler() { + return handler; + } } \ No newline at end of file diff --git a/src/main/java/track/messenger/net/SessionException.java b/src/main/java/track/messenger/net/SessionException.java new file mode 100644 index 00000000..f641fd16 --- /dev/null +++ b/src/main/java/track/messenger/net/SessionException.java @@ -0,0 +1,15 @@ +package track.messenger.net; + +/** + * Tehnotrack + * track.messenger.net + *

+ * Created by ilya on 13.04.17. + */ +public class SessionException extends Exception { + + public SessionException(String message) { + super(message); + } + +} diff --git a/src/main/java/track/messenger/net/SessionsHandler.java b/src/main/java/track/messenger/net/SessionsHandler.java new file mode 100644 index 00000000..e2645867 --- /dev/null +++ b/src/main/java/track/messenger/net/SessionsHandler.java @@ -0,0 +1,134 @@ +package track.messenger.net; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import track.messenger.commands.Command; +import track.messenger.commands.CommandFactory; +import track.messenger.messages.Message; +import track.messenger.messages.Type; +import track.messenger.messages.responses.StatusMessage; +import track.messenger.messages.responses.TextMessage; +import track.messenger.models.User; + +import java.net.Socket; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Tehnotrack + * track.messenger.net + *

+ * Created by ilya on 12.04.17. + */ + +public class SessionsHandler { + + private static Logger log = LoggerFactory.getLogger(SessionsHandler.class); + + private Protocol protocol; + private ExecutorService executorPool; + + private volatile LinkedList sessions; + private Map authSessions = new ConcurrentHashMap<>(); + private Map sessionIDMap = new ConcurrentHashMap<>(); + + public SessionsHandler(Protocol protocol, int threadsCount) { + this.protocol = protocol; + sessions = new LinkedList<>(); + executorPool = Executors.newFixedThreadPool(threadsCount); + } + + public void start() { + while (true) { + if (sessions.isEmpty()) { + continue; + } + + Session session = sessions.removeLast(); + if (!session.isAlive()) { + closeSession(session.getId()); + continue; + } + + sessions.push(session); + + Message message = receiveMessage(session); + if (!session.isAlive()) { + log.error("Session dead"); + } + if (message == null) { + continue; + } + + executorPool.submit(() -> proceedMessage(session, message)); + } + } + + private void proceedMessage(Session session, Message msg) { + log.info("Proceed message : " + msg.toString()); + + Command cmd = CommandFactory.getInstance().create(msg.getType()); + + if (cmd == null) { + session.send(new StatusMessage(StatusMessage.Status.FAIL)); + } else { + cmd.execute(session, msg); + } + } + + private Message receiveMessage(Session session) { + Message msg = null; + try { + msg = session.readMessage(); + } catch (ProtocolException e) { + if (session.isAlive()) { + // TODO send status message + log.warn("Protocol exception; send StatusMessage "); + session.send(new StatusMessage(StatusMessage.Status.FAIL)); + } + log.warn(e.getMessage()); + } + return msg; + } + + private void closeSession(long sessionId) { + Session session = sessionIDMap.get(sessionId); + if (session.getUser() != null) { + log.info("Deauth user : " + session.getUser().toString()); + authSessions.remove(session.getUser()); + } + sessionIDMap.remove(sessionId); + session.close(); + log.info("Close session : " + session.toString()); + } + + public void createSession(Socket socket) { + Session session = new Session(socket, protocol, this); + sessionIDMap.put(session.getId(), session); + sessions.push(session); + + log.info("Create session : " + session.toString()); + } + + public void authSession(long sessionId, User user) throws SessionException { + synchronized (authSessions) { + if (authSessions.containsKey(user)) { + throw new SessionException("This user has already logged in"); + } + authSessions.put(user, sessionIDMap.get(sessionId)); + log.info("Auth user : " + user.toString()); + } + } + + public Map getAuthSessions() { + return authSessions; + } + + public void sendMessageInChat(Session session, TextMessage storedMessage) { + Command cmd = CommandFactory.getInstance().create(Type.MSG_SEND_TEXT); + cmd.execute(session, storedMessage); + } +} diff --git a/src/main/java/track/messenger/store/DatabaseConfiguration.java b/src/main/java/track/messenger/store/DatabaseConfiguration.java new file mode 100644 index 00000000..d35ba0e6 --- /dev/null +++ b/src/main/java/track/messenger/store/DatabaseConfiguration.java @@ -0,0 +1,32 @@ +package track.messenger.store; + +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; + +/** + * Tehnotrack + * track.messenger.store + *

+ * Created by ilya on 13.04.17. + */ +@Configuration +public class DatabaseConfiguration { + + @Bean + public DataSource getDataSource( + @Value("${track.messenger.store.jdbc:}") String jdbcUrl, + @Value("${track.messenger.store.username:}") String username, + @Value("${track.messenger.store.password:}") String password) { + HikariConfig config = new HikariConfig(); + config.setDriverClassName(org.h2.Driver.class.getName()); + config.setJdbcUrl("jdbc:h2:" + jdbcUrl); + config.setUsername(username); + config.setPassword(password); + return new HikariDataSource(config); + } +} diff --git a/src/main/java/track/messenger/store/DatabaseMessageStore.java b/src/main/java/track/messenger/store/DatabaseMessageStore.java new file mode 100644 index 00000000..415bc33d --- /dev/null +++ b/src/main/java/track/messenger/store/DatabaseMessageStore.java @@ -0,0 +1,322 @@ +package track.messenger.store; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import track.messenger.messages.BaseTextMessage; +import track.messenger.messages.Message; +import track.messenger.messages.responses.TextMessage; +import track.messenger.models.Chat; +import track.messenger.models.User; + +import javax.annotation.PostConstruct; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.AbstractMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Tehnotrack + * track.messenger.store + *

+ * Created by ilya on 14.04.17. + */ +@Repository +public class DatabaseMessageStore implements MessageStore { + + private Logger log = LoggerFactory.getLogger(DatabaseMessageStore.class); + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private Connection connection; + + @Autowired + private UserStore userStore; + + private PreparedStatement getChatsByUserIdStatement; + private PreparedStatement getChatByIdStatement; + private PreparedStatement getMessagesFromChatStatement; + private PreparedStatement getMessageByIdStatement; + private PreparedStatement addMessageStatement; + private PreparedStatement addUserToChatStatement; + private PreparedStatement getUsersInChatStatement; + private PreparedStatement createChatStatement; + + + @PostConstruct + public void postConstruct() { + initSchema(); + prepareStatements(); + } + + + private void initSchema() { + log.info("Init message schema"); + jdbcTemplate.execute( + "CREATE TABLE IF NOT EXISTS Messenger.Messages (" + + "id INTEGER NOT NULL AUTO_INCREMENT," + + "sender_id INTEGER NOT NULL," + + "chat_id INTEGER NOT NULL," + + "text VARCHAR NOT NULL)"); + + jdbcTemplate.execute( + "CREATE TABLE IF NOT EXISTS Messenger.Chats (" + + "id INTEGER NOT NULL AUTO_INCREMENT," + + "title VARCHAR NOT NULL," + + "admin_id INTEGER NOT NULL)"); + + jdbcTemplate.execute( + "CREATE TABLE IF NOT EXISTS Messenger.UsersChats (" + + "chat_id INTEGER NOT NULL," + + "user_id INTEGER NOT NULL)"); + + log.info("Message schema has initialized"); + + } + + private void prepareStatements() { + try { + log.info("Prepare message statements"); + + getChatsByUserIdStatement = connection.prepareStatement( + "SELECT id, title FROM Messenger.Chats " + + "INNER JOIN Messenger.UsersChats ON Messenger.Chats.id = Messenger.UsersChats.chat_id " + + "WHERE Messenger.UsersChats.user_id = ?"); + + getChatByIdStatement = connection.prepareStatement( + "SELECT id, title, admin_id " + + "FROM Messenger.Chats WHERE Messenger.Chats.id = ?"); + + getUsersInChatStatement = connection.prepareStatement( + "SELECT user_id FROM Messenger.UsersChats WHERE chat_id = ?"); + + getMessageByIdStatement = connection.prepareStatement( + "SELECT id, sender_id, text " + + "FROM Messenger.Messages " + + "WHERE Messenger.Messages.chat_id = ?"); + + addMessageStatement = connection.prepareStatement( + "INSERT INTO Messenger.Messages(SENDER_ID, TEXT, CHAT_ID) VALUES (?, ?, ?)", + new String[]{"id"}); + + addUserToChatStatement = connection.prepareStatement( + "INSERT INTO MESSENGER.USERSCHATS(CHAT_ID, USER_ID) VALUES (?, ?)", + new String[]{"id"}); + + createChatStatement = connection.prepareStatement( + "INSERT INTO Messenger.Chats(TITLE, ADMIN_ID) VALUES (?, ?)", + new String[]{"id"}); + + getMessagesFromChatStatement = connection.prepareStatement( + "SELECT id FROM MESSENGER.Messages WHERE Messenger.Messages.CHAT_ID = ?"); + + log.info("Message statements have initialized"); + + } catch (SQLException ex) { + log.error(ex.getMessage()); + } + } + + @Override + public List> getChatsByUserId(Long userId) { + List> chats = new LinkedList<>(); + try { + log.info("Get user by id"); + getChatsByUserIdStatement.setLong(1, userId); + + ResultSet result = getChatsByUserIdStatement.executeQuery(); + while (result.next()) { + chats.add(new AbstractMap.SimpleEntry<>(result.getLong("id"), + result.getString("title"))); + } + log.info("User has found"); + } catch (SQLException e) { + log.error(e.getMessage()); + } + + return chats; + } + + @Override + public Chat getChatById(Long chatId) { + Chat chat = null; + + try { + getChatByIdStatement.setLong(1, chatId); + ResultSet result = getChatByIdStatement.executeQuery(); + if (result.next()) { + + getUsersInChatStatement.setLong(1, chatId); + ResultSet resultUsersSet = getUsersInChatStatement.executeQuery(); + List users = new LinkedList<>(); + while (resultUsersSet.next()) { + users.add(userStore.getUserById(resultUsersSet.getLong("user_id"))); + } + + long adminId = result.getLong("admin_id"); + String title = result.getString("title"); + User admin = userStore.getUserById(adminId); + + chat = new Chat(chatId, title, admin, users); + } + } catch (SQLException e) { + log.error(e.getMessage()); + } + + return chat; + } + + @Override + public List getMessagesFromChat(Long chatId) { + List resultList = null; + try { + + getMessagesFromChatStatement.setLong(1, chatId); + + ResultSet result = getMessagesFromChatStatement.executeQuery(); + + resultList = new LinkedList<>(); + + while (result.next()) { + resultList.add(result.getLong("id")); + } + + } catch (SQLException e) { + log.error(e.getMessage()); + } + + return resultList; + } + + @Override + public Message getMessageById(Long messageId) { + Message msg = null; + try { + + getMessageByIdStatement.setLong(1, messageId); + + ResultSet result = getMessageByIdStatement.executeQuery(); + + if (result.next()) { + msg = new TextMessage( + messageId, + result.getLong("sender_id"), + result.getLong("chat_id"), + result.getString("text")); + } + + } catch (SQLException e) { + log.error(e.getMessage()); + } + return msg; + } + + @Override + public TextMessage addMessage(Long chatId, BaseTextMessage message) { + TextMessage msg = null; + + try { + + addMessageStatement.setLong(1, message.getSenderId()); + addMessageStatement.setString(2, message.getText()); + addMessageStatement.setLong(3, chatId); + + addMessageStatement.execute(); + + ResultSet resultSet = addMessageStatement.getGeneratedKeys(); + + if (resultSet.next()) { + msg = new TextMessage( + resultSet.getLong(1), + message.getSenderId(), + chatId, + message.getText()); + } + + } catch (SQLException e) { + log.error(e.getMessage()); + + } + + return msg; + } + + @Override + public boolean addUserToChat(Long userId, Long chatId) { + boolean ok = true; + + try { + addUserToChatStatement.setLong(1, chatId); + addUserToChatStatement.setLong(2, userId); + + addUserToChatStatement.execute(); + ResultSet resultSet = addUserToChatStatement.getGeneratedKeys(); + + if (!resultSet.next()) { + ok = false; + } + + } catch (SQLException e) { + log.error(e.getMessage()); + ok = false; + } + + return ok; + } + + @Override + public Chat createChat(Long adminId, List usersIds, String title) { + Chat chat = null; + + boolean ok = true; + + try { + + createChatStatement.setString(1, title); + createChatStatement.setLong(2, adminId); + + createChatStatement.execute(); + ResultSet resultSet = addMessageStatement.getGeneratedKeys(); + + Long chatId = -1L; + if (resultSet.next()) { + chatId = resultSet.getLong(1); + } else { + return null; + } + + List users = new LinkedList<>(); + for (Long userId : usersIds) { + addUserToChat(userId, chatId); + + User user = userStore.getUserById(userId); + if (user == null) { + ok = false; + } + users.add(user); + } + addUserToChat(adminId, chatId); + + User admin = userStore.getUserById(adminId); + if (admin == null) { + ok = false; + } + + if (ok) { + chat = new Chat(chatId, title, admin, users); + } + } catch (SQLException e) { + log.error(e.getMessage()); + } + + return chat; + } +} diff --git a/src/main/java/track/messenger/store/DatabaseUserStore.java b/src/main/java/track/messenger/store/DatabaseUserStore.java new file mode 100644 index 00000000..a4473bd2 --- /dev/null +++ b/src/main/java/track/messenger/store/DatabaseUserStore.java @@ -0,0 +1,181 @@ +package track.messenger.store; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import track.messenger.models.User; + +import javax.annotation.PostConstruct; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; + +/** + * Tehnotrack + * track.messenger.store + *

+ * Created by ilya on 13.04.17. + */ +@Repository +public class DatabaseUserStore implements UserStore { + + private static Logger log = LoggerFactory.getLogger(DatabaseUserStore.class); + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private Connection connection; + + private PreparedStatement addUserStatement; + private PreparedStatement updateUserStatement; + private PreparedStatement getUserStatement; + private PreparedStatement getUserByIdStatement; + private PreparedStatement getAllUsers; + + @PostConstruct + public void postConstruct() { + initSchema(); + prepareStatements(); + } + + private void initSchema() { + log.info("Init user schema"); + String createTableQuery = + "CREATE TABLE IF NOT EXISTS Messenger.Users (" + + "id INTEGER NOT NULL AUTO_INCREMENT," + + "login VARCHAR UNIQUE NOT NULL," + + "password VARCHAR NOT NULL)"; + + jdbcTemplate.execute(createTableQuery); + log.info("User schema has initialized"); + } + + private void prepareStatements() { + try { + log.info("Prepare statements"); + addUserStatement = connection.prepareStatement("INSERT INTO Messenger.Users(login, password)" + + " VALUES (?, ?)", new String[]{"id"}); + + + updateUserStatement = connection.prepareStatement("UPDATE Messenger.Users SET login = ? , " + + "password = ? WHERE id = ?"); + + getUserStatement = connection.prepareStatement("SELECT * FROM Messenger.Users WHERE login = ?" + + " AND password = ?"); + + getUserByIdStatement = connection.prepareStatement("SELECT * FROM Messenger.Users WHERE id = ?"); + + getAllUsers = connection.prepareStatement("SELECT id, login FROM Messenger.Users"); + + log.info("Statements has prepared"); + } catch (SQLException e) { + log.error(e.getMessage()); + } + } + + @Override + public User addUser(String login, String password) { + User user = null; + try { + log.info("Add user"); + addUserStatement.setString(1, login); + addUserStatement.setString(2, password); + addUserStatement.execute(); + ResultSet resultSet = addUserStatement.getGeneratedKeys(); + + while (resultSet.next()) { + user = new User(resultSet.getLong(1), login, null); + } + + log.info(String.format("User has added {0}", user)); + } catch (SQLException e) { + log.error(e.getMessage()); + } + + return user; + } + + @Override + public User updateUser(User user) { + try { + log.info("Update user"); + updateUserStatement.setString(1, user.getNickname()); + updateUserStatement.setString(2, user.getPassword()); + updateUserStatement.setLong(3, user.getId()); + user = getUserById(user.getId()); + log.info("User has updated"); + } catch (SQLException ex) { + log.error(ex.getMessage()); + user = null; + } + return user; + } + + @Override + public User getUser(String login, String password) { + User user = null; + try { + log.info("Get user by login-password"); + getUserStatement.setString(1, login); + getUserStatement.setString(2, password); + + ResultSet result = getUserStatement.executeQuery(); + if (result.next()) { + user = new User(result.getLong("id"), result.getString("login"), + result.getString("password")); + log.info("User has found"); + } + } catch (SQLException e) { + log.error(e.getMessage()); + } + + return user; + } + + @Override + public User getUserById(Long id) { + User user = null; + try { + log.info("Get user by id"); + getUserByIdStatement.setLong(1, id); + + ResultSet result = getUserByIdStatement.executeQuery(); + if (result.next()) { + user = new User(result.getLong("id"), result.getString("login"), + result.getString("password")); + } + log.info("User has found"); + } catch (SQLException e) { + log.error(e.getMessage()); + } + + return user; + } + + @Override + public List getUsers() { + List result = null; + try { + log.info("Get all users"); + + ResultSet resultSet = getAllUsers.executeQuery(); + + result = new LinkedList<>(); + + while (resultSet.next()) { + result.add(new User(resultSet.getLong("id"), resultSet.getString("login"), null)); + } + + } catch (SQLException ex) { + log.error(ex.getMessage()); + } + + return result; + } +} diff --git a/src/main/java/track/messenger/store/MessageStore.java b/src/main/java/track/messenger/store/MessageStore.java index 1001ad1b..3038e152 100644 --- a/src/main/java/track/messenger/store/MessageStore.java +++ b/src/main/java/track/messenger/store/MessageStore.java @@ -1,19 +1,21 @@ package track.messenger.store; +import track.messenger.messages.BaseTextMessage; import track.messenger.messages.Message; +import track.messenger.messages.responses.TextMessage; +import track.messenger.models.Chat; import java.util.List; +import java.util.Map; public interface MessageStore { - /** - * получаем список ид пользователей заданного чата - */ - List getChatsByUserId(Long userId); + + List> getChatsByUserId(Long userId); /** * получить информацию о чате */ - //Chat getChatById(Long chatId); + Chat getChatById(Long chatId); /** * Список сообщений из чата @@ -28,11 +30,12 @@ public interface MessageStore { /** * Добавить сообщение в чат */ - void addMessage(Long chatId, Message message); + TextMessage addMessage(Long chatId, BaseTextMessage message); /** * Добавить пользователя к чату */ - void addUserToChat(Long userId, Long chatId); + boolean addUserToChat(Long userId, Long chatId); + Chat createChat(Long adminId, List usersIds, String title); } diff --git a/src/main/java/track/messenger/store/MessengerDAO.java b/src/main/java/track/messenger/store/MessengerDAO.java new file mode 100644 index 00000000..4037739f --- /dev/null +++ b/src/main/java/track/messenger/store/MessengerDAO.java @@ -0,0 +1,61 @@ +package track.messenger.store; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import javax.annotation.PostConstruct; +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + + +/** + * Tehnotrack + * track.messenger.store + *

+ * Created by ilya on 13.04.17. + */ +@Repository +public class MessengerDAO { + private static Logger log = LoggerFactory.getLogger(MessengerDAO.class); + + @Autowired + private DataSource dataSource; + + private JdbcTemplate jdbcTemplate; + + private Connection connection; + + private static final String SCHEMA_NAME = "Messenger"; + + @PostConstruct + public void postConstruct() { + jdbcTemplate = new JdbcTemplate(dataSource); + initSchema(); + try { + connection = dataSource.getConnection(); + } catch (SQLException e) { + log.error(e.getSQLState()); + log.error(e.getMessage()); + } + } + + private void initSchema() { + jdbcTemplate.execute("CREATE SCHEMA IF NOT EXISTS " + SCHEMA_NAME); + } + + @Bean + public JdbcTemplate getJdbc() { + return jdbcTemplate; + } + + @Bean + public Connection getConnection() { + return connection; + } + +} diff --git a/src/main/java/track/messenger/store/UserStore.java b/src/main/java/track/messenger/store/UserStore.java index 174dfcfc..e7a61ebc 100644 --- a/src/main/java/track/messenger/store/UserStore.java +++ b/src/main/java/track/messenger/store/UserStore.java @@ -1,13 +1,15 @@ package track.messenger.store; -import track.messenger.User; +import track.messenger.models.User; + +import java.util.List; public interface UserStore { /** * Добавить пользователя в хранилище * Вернуть его же */ - User addUser(User user); + User addUser(String login, String password); /** * Обновить информацию о пользователе @@ -27,4 +29,6 @@ public interface UserStore { * return null if user not found */ User getUserById(Long id); + + List getUsers(); } diff --git a/src/main/java/track/messenger/teacher/client/MessengerClient.java b/src/main/java/track/messenger/teacher/client/MessengerClient.java index 26cace9d..c31aa7f1 100644 --- a/src/main/java/track/messenger/teacher/client/MessengerClient.java +++ b/src/main/java/track/messenger/teacher/client/MessengerClient.java @@ -2,9 +2,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import track.messenger.User; import track.messenger.messages.Message; -import track.messenger.messages.TextMessage; +import track.messenger.messages.Type; +import track.messenger.messages.requests.*; +import track.messenger.messages.responses.*; +import track.messenger.models.User; import track.messenger.net.BinaryProtocol; import track.messenger.net.Protocol; import track.messenger.net.ProtocolException; @@ -14,7 +16,10 @@ import java.io.OutputStream; import java.net.Socket; import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.Scanner; +import java.util.stream.Collectors; /** @@ -26,7 +31,7 @@ public class MessengerClient { /** * Механизм логирования позволяет более гибко управлять записью данных в лог (консоль, файл и тд) */ - static Logger LOG = LoggerFactory.getLogger(MessengerClient.class); + static Logger log = LoggerFactory.getLogger(MessengerClient.class); /** * Протокол, хост и порт инициализируются из конфига @@ -39,6 +44,7 @@ public class MessengerClient { /** * С каждым сокетом связано 2 канала in/out */ + private Socket socket; private InputStream in; private OutputStream out; @@ -67,25 +73,20 @@ public void setHost(String host) { } public void initSocket() throws IOException { - Socket socket = new Socket(host, port); + socket = new Socket(host, port); in = socket.getInputStream(); out = socket.getOutputStream(); - /* - Тред "слушает" сокет на наличие входящих сообщений от сервера - */ Thread socketListenerThread = new Thread(() -> { - LOG.info("Starting listener thread..."); + log.info("Starting listener thread..."); while (!Thread.currentThread().isInterrupted()) { try { - // Здесь поток блокируется на ожидании данных - Message msg = protocol.decode(in); + Message msg = protocol.deserialize(in); if (msg != null) { onMessage(msg); } - } catch (Exception e) { - LOG.error("Failed to process connection: {}", e); - e.printStackTrace(); + } catch (ProtocolException e) { + log.error("Failed to process connection: {}", e.getMessage()); Thread.currentThread().interrupt(); } } @@ -98,7 +99,61 @@ public void initSocket() throws IOException { * Реагируем на входящее сообщение */ public void onMessage(Message msg) { - LOG.info("Message received: {}", msg); + log.info("Message received: {}", msg); + + if (msg.getType() == Type.MSG_SEND_TEXT) { + TextMessage textMessage = (TextMessage) msg; + System.out.println(String.format("Message from %d : %s", textMessage.getSenderId(), textMessage.getText())); + return; + } + + StatusMessage status = (StatusMessage) msg; + if (status.getStatus() == StatusMessage.Status.OK) { + log.info("Operation succeeded"); + + switch (msg.getType()) { + + case MSG_STATUS: + //... do nothing + break; + + case MSG_CHAT_LIST_RESULT: + ChatListResultMessage chatListMessage = (ChatListResultMessage) msg; + for (Map.Entry entry : chatListMessage.getChats()) { + System.out.print(String.format("--- %d : %s --- ", entry.getKey(), entry.getValue())); + } + System.out.println(); + break; + + case MSG_CHAT_HIST_RESULT: + ChatHistoryResultMessage chatHistoryMessage = (ChatHistoryResultMessage) msg; + for (Long id : chatHistoryMessage.getMessagesId()) { + System.out.print(String.format("%d, ", id)); + } + System.out.println(); + break; + + case MSG_INFO_RESULT: + InfoResultMessage infoMessage = (InfoResultMessage) msg; + currentUser = infoMessage.getUser(); + break; + + case MSG_USERS_LIST_RESULT: + UserListResultMessage userListMessage = (UserListResultMessage) msg; + System.out.println("Users : "); + for (User user : userListMessage.getUsers()) { + System.out.print(String.format("--- %d : %s --- ", user.getId(), user.getNickname())); + } + System.out.println(); + break; + + default: + log.error("Catch unknown message from server!"); + } + + } else { + log.info(String.format("Operation failed because of : {}", status.getStatus())); + } } /** @@ -107,25 +162,133 @@ public void onMessage(Message msg) { */ public void processInput(String line) throws IOException, ProtocolException { String[] tokens = line.split(" "); - LOG.info("Tokens: {}", Arrays.toString(tokens)); + log.info("Tokens: {}", Arrays.toString(tokens)); String cmdType = tokens[0]; + Long chatId = -1L; + switch (cmdType) { case "/login": - // TODO: реализация + if (!checkLoggedIn(false)) { + break; + } + if (tokens.length != 3) { + System.out.println("Need "); + break; + } + LoginMessage loginMessage = new LoginMessage(tokens[1], tokens[2]); + send(loginMessage); + + break; + + case "/createuser": + if (!checkLoggedIn(false)) { + break; + } + if (tokens.length != 3) { + System.out.println("Need "); + break; + } + UserCreateMessage userCreateMessage = new UserCreateMessage(tokens[1], tokens[2]); + send(userCreateMessage); break; + + case "/userslist": + send(new UserListMessage()); + break; + case "/help": - // TODO: реализация + System.out.println("Alright, this is help string."); break; + case "/text": - // FIXME: пример реализации для простого текстового сообщения - TextMessage sendMessage = new TextMessage(currentUser.getId()); - sendMessage.setText(tokens[1]); + if (!checkLoggedIn(true)) { + break; + } + + try { + chatId = Long.parseLong(tokens[1]); + } catch (NumberFormatException e) { + System.out.println("Can't parse chatId"); + break; + } + + ChatMessage sendMessage = new ChatMessage(currentUser.getId(), chatId, tokens[2]); send(sendMessage); break; - // TODO: implement another types from wiki + + case "/info": + + //TODO !!! + log.error("INFO NOT IMPLEMENTED"); + break; + + case "/chatslist": + + if (!checkLoggedIn(true)) { + break; + } + + ChatListMessage chatListMessage = new ChatListMessage(currentUser.getId()); + send(chatListMessage); + break; + + case "/createchat": + if (!checkLoggedIn(true)) { + break; + } + if (tokens.length != 3) { + System.out.println("Need "); + break; + } + + List users = Arrays.stream(tokens[2].split(",")) + .map(Long::parseLong) + .collect(Collectors.toList()); + ChatCreateMessage chatCreateMessage = new ChatCreateMessage(currentUser.getId(), tokens[1], users); + send(chatCreateMessage); + break; + + case "/chathist": + + if (!checkLoggedIn(true)) { + break; + } + + if (tokens.length != 2) { + System.out.println("Need "); + break; + } + try { + chatId = Long.parseLong(tokens[1]); + } catch (NumberFormatException e) { + System.out.println("Can't parse chatId"); + break; + } + + ChatHistoryMessage chatHistoryMessage = new ChatHistoryMessage(currentUser.getId(), chatId); + send(chatHistoryMessage); + + break; + default: - LOG.error("Invalid input: " + line); + log.error("Invalid input: " + line); + } + } + + private boolean checkLoggedIn(boolean need) { + if (need) { + if (currentUser == null) { + System.out.println("Need to be logged in"); + return false; + } + return true; + } else { + if (currentUser != null) { + System.out.println("You are already logged in"); + return false; + } + return true; } } @@ -133,17 +296,16 @@ public void processInput(String line) throws IOException, ProtocolException { * Отправка сообщения в сокет клиент -> сервер */ public void send(Message msg) throws IOException, ProtocolException { - LOG.info(msg.toString()); - out.write(protocol.encode(msg)); - out.flush(); // принудительно проталкиваем буфер с данными + log.info(msg.toString()); + out.write(protocol.serialize(msg)); + out.flush(); } public void close() { try { - in.close(); - out.close(); + socket.close(); } catch (IOException ex) { - LOG.error(ex.getMessage()); + log.error(ex.getMessage()); } } @@ -159,8 +321,8 @@ public static void main(String[] args) throws Exception { // Цикл чтения с консоли Scanner scanner = new Scanner(System.in); - System.out.println("$"); while (true) { + System.out.print("$ "); String input = scanner.nextLine(); if ("q".equals(input)) { return; @@ -168,16 +330,16 @@ public static void main(String[] args) throws Exception { try { client.processInput(input); } catch (ProtocolException | IOException e) { - LOG.error("Failed to process user input", e); + log.error("Failed to process user input", e); } } } catch (Exception e) { - LOG.error("Application failed.", e); + log.error("Application failed.", e); } finally { - if (client != null) { - // TODO - client.close(); - } + // TODO + log.info("Close socket"); + client.close(); + } } } \ No newline at end of file diff --git a/src/main/java/track/spring/server/SpringContainerExample.java b/src/main/java/track/spring/server/SpringContainerExample.java index 55d02b17..be3e355c 100644 --- a/src/main/java/track/spring/server/SpringContainerExample.java +++ b/src/main/java/track/spring/server/SpringContainerExample.java @@ -29,10 +29,10 @@ public static void main(String[] args) throws Exception { // // log.info("Server isRunning:" + server.isRunning()); // -// SocketServer server2 = (SocketServer)context.getBean("socketServer"); -// log.info("Server isRunning:" + server2.isRunning()); +// SocketServer server = (SocketServer)context.getBean("socketServer"); +// log.info("Server isRunning:" + server.isRunning()); // -// server2.destroy(); +// server.destroy(); // log.info("Server isRunning:" + server.isRunning()); diff --git a/src/main/resources/app.properties b/src/main/resources/app.properties index ee852e43..e2c1e6d2 100644 --- a/src/main/resources/app.properties +++ b/src/main/resources/app.properties @@ -1,2 +1,7 @@ port=19000 -poolSize=50 \ No newline at end of file +poolSize=50 +track.messenger.net.port=4242 +track.messenger.net.threads=2 +track.messenger.store.jdbc=/run/media/ilya/Data/Projects/MIPT/Tehnotrack/track17-spring/Messenger +track.messenger.store.username=root +track.messenger.store.password=root diff --git a/src/main/resources/spring-config.xml b/src/main/resources/spring-config.xml index 0b4e94ee..ba1209b5 100644 --- a/src/main/resources/spring-config.xml +++ b/src/main/resources/spring-config.xml @@ -6,8 +6,7 @@ - - + @@ -27,11 +26,6 @@ - - - - - - + \ No newline at end of file