From 08acb3ddc0d27678d1a58516cba647c1120b7826 Mon Sep 17 00:00:00 2001 From: Julia Butenhoff Date: Thu, 7 Jul 2022 14:02:42 -0500 Subject: [PATCH] Added JavaGrinko's TcpController Library - Started implementing it into the ArkOneController class - Needs testing to verify it can communicate with the game --- .../ArkOne/ArkOneController.java | 35 ++++++ .../UBFunkeysServerApplication.java | 3 +- .../starter/TcpServerAutoConfiguration.java | 31 +++++ .../spring/starter/TcpServerProperties.java | 32 ++++++ .../javagrinko/spring/tcp/Connection.java | 23 ++++ .../java/javagrinko/spring/tcp/Server.java | 13 +++ .../javagrinko/spring/tcp/TcpConnection.java | 96 ++++++++++++++++ .../javagrinko/spring/tcp/TcpController.java | 11 ++ .../tcp/TcpControllerBeanPostProcessor.java | 52 +++++++++ .../javagrinko/spring/tcp/TcpHandler.java | 7 ++ .../java/javagrinko/spring/tcp/TcpServer.java | 108 ++++++++++++++++++ ...pServerAutoStarterApplicationListener.java | 26 +++++ 12 files changed, 435 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java create mode 100644 src/main/java/javagrinko/spring/starter/TcpServerAutoConfiguration.java create mode 100644 src/main/java/javagrinko/spring/starter/TcpServerProperties.java create mode 100644 src/main/java/javagrinko/spring/tcp/Connection.java create mode 100644 src/main/java/javagrinko/spring/tcp/Server.java create mode 100644 src/main/java/javagrinko/spring/tcp/TcpConnection.java create mode 100644 src/main/java/javagrinko/spring/tcp/TcpController.java create mode 100644 src/main/java/javagrinko/spring/tcp/TcpControllerBeanPostProcessor.java create mode 100644 src/main/java/javagrinko/spring/tcp/TcpHandler.java create mode 100644 src/main/java/javagrinko/spring/tcp/TcpServer.java create mode 100644 src/main/java/javagrinko/spring/tcp/TcpServerAutoStarterApplicationListener.java diff --git a/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java new file mode 100644 index 0000000..8f6acb3 --- /dev/null +++ b/src/main/java/com/icedberries/UBFunkeysServer/ArkOne/ArkOneController.java @@ -0,0 +1,35 @@ +package com.icedberries.UBFunkeysServer.ArkOne; + +import javagrinko.spring.tcp.Connection; +import javagrinko.spring.tcp.TcpController; +import javagrinko.spring.tcp.TcpHandler; + +import java.io.IOException; + +@TcpController +public class ArkOneController implements TcpHandler { + + @Override + public void receiveData(Connection connection, byte[] data) { + //TODO: IMPLEMENT PLUGIN CHECKING AND FORWARDING HERE + //Currently just echos back the received data until implemented + String s = new String(data); + try { + connection.send(s.toUpperCase().getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void connectEvent(Connection connection) { + // Just log connections + System.out.println("[ArkOne][EVENT] Client connection from: " + connection.getAddress().getCanonicalHostName()); + } + + @Override + public void disconnectEvent(Connection connection) { + // Just log disconnections + System.out.println("[ArkOne][EVENT] Client disconnected: " + connection.getAddress().getCanonicalHostName()); + } +} \ No newline at end of file diff --git a/src/main/java/com/icedberries/UBFunkeysServer/UBFunkeysServerApplication.java b/src/main/java/com/icedberries/UBFunkeysServer/UBFunkeysServerApplication.java index 3f6cdd4..bffbcea 100644 --- a/src/main/java/com/icedberries/UBFunkeysServer/UBFunkeysServerApplication.java +++ b/src/main/java/com/icedberries/UBFunkeysServer/UBFunkeysServerApplication.java @@ -4,8 +4,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; -@SpringBootApplication -@ComponentScan(basePackages = "com.icedberries") +@SpringBootApplication(scanBasePackages = {"com.icedberries", "javagrinko.spring"}) public class UBFunkeysServerApplication { public static void main(String[] args) { diff --git a/src/main/java/javagrinko/spring/starter/TcpServerAutoConfiguration.java b/src/main/java/javagrinko/spring/starter/TcpServerAutoConfiguration.java new file mode 100644 index 0000000..8acb6cb --- /dev/null +++ b/src/main/java/javagrinko/spring/starter/TcpServerAutoConfiguration.java @@ -0,0 +1,31 @@ +package javagrinko.spring.starter; + +import javagrinko.spring.tcp.Server; +import javagrinko.spring.tcp.TcpControllerBeanPostProcessor; +import javagrinko.spring.tcp.TcpServer; +import javagrinko.spring.tcp.TcpServerAutoStarterApplicationListener; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(TcpServerProperties.class) +@ConditionalOnProperty(prefix = "javagrinko.tcp-server", name = {"port", "auto-start"}) +public class TcpServerAutoConfiguration { + + @Bean + TcpServerAutoStarterApplicationListener tcpServerAutoStarterApplicationListener() { + return new TcpServerAutoStarterApplicationListener(); + } + + @Bean + TcpControllerBeanPostProcessor tcpControllerBeanPostProcessor() { + return new TcpControllerBeanPostProcessor(); + } + + @Bean + Server server(){ + return new TcpServer(); + } +} diff --git a/src/main/java/javagrinko/spring/starter/TcpServerProperties.java b/src/main/java/javagrinko/spring/starter/TcpServerProperties.java new file mode 100644 index 0000000..2f807dc --- /dev/null +++ b/src/main/java/javagrinko/spring/starter/TcpServerProperties.java @@ -0,0 +1,32 @@ +package javagrinko.spring.starter; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "tcp.server") +public class TcpServerProperties { + + @Value("${tcp.server.port}") + private Integer port = 1234; + + @Value("${tcp.server.auto-start}") + private Boolean autoStart = true; + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public boolean getAutoStart() { + return autoStart; + } + + public void setAutoStart(boolean autoStart) { + this.autoStart = autoStart; + } +} diff --git a/src/main/java/javagrinko/spring/tcp/Connection.java b/src/main/java/javagrinko/spring/tcp/Connection.java new file mode 100644 index 0000000..10ce6e1 --- /dev/null +++ b/src/main/java/javagrinko/spring/tcp/Connection.java @@ -0,0 +1,23 @@ +package javagrinko.spring.tcp; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.InetAddress; + +public interface Connection { + InetAddress getAddress(); + void send(byte[] bytes) throws IOException; + void addListener(Listener listener); + void start(); + void close() throws IOException; + interface Listener { + void messageReceived(Connection connection, byte[] bytes) + throws InvocationTargetException, IllegalAccessException; + + void connected(Connection connection) + throws InvocationTargetException, IllegalAccessException; + + void disconnected(Connection connection) + throws InvocationTargetException, IllegalAccessException; + } +} diff --git a/src/main/java/javagrinko/spring/tcp/Server.java b/src/main/java/javagrinko/spring/tcp/Server.java new file mode 100644 index 0000000..93b37d8 --- /dev/null +++ b/src/main/java/javagrinko/spring/tcp/Server.java @@ -0,0 +1,13 @@ +package javagrinko.spring.tcp; + + +import java.util.List; + +public interface Server { + int getConnectionsCount(); + void setPort(Integer port); + void start(); + void stop(); + List getConnections(); + void addListener(Connection.Listener listener); +} diff --git a/src/main/java/javagrinko/spring/tcp/TcpConnection.java b/src/main/java/javagrinko/spring/tcp/TcpConnection.java new file mode 100644 index 0000000..3304b25 --- /dev/null +++ b/src/main/java/javagrinko/spring/tcp/TcpConnection.java @@ -0,0 +1,96 @@ +package javagrinko.spring.tcp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.net.InetAddress; +import java.net.Socket; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +public class TcpConnection implements Connection { + private static Log logger = LogFactory.getLog(TcpConnection.class); + private InputStream inputStream; + private OutputStream outputStream; + private Socket socket; + private List listeners = new CopyOnWriteArrayList<>(); + + TcpConnection(Socket socket) { + this.socket = socket; + try { + inputStream = socket.getInputStream(); + outputStream = socket.getOutputStream(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public InetAddress getAddress() { + return socket.getInetAddress(); + } + + @Override + public void send(byte[] bytes) throws IOException { + outputStream.write(bytes); + logger.trace("Sent message"); + if (logger.isTraceEnabled()) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X ", b)); + } + logger.trace(" ==> " + sb.toString()); + } + } + + @Override + public void addListener(Listener listener) { + listeners.add(listener); + } + + @Override + public void start() { + new Thread(() -> { + while (true) { + byte buf[] = new byte[64 * 1024]; + try { + int count = inputStream.read(buf); + if (count > 0) { + byte[] bytes = Arrays.copyOf(buf, count); + for (Listener listener : listeners) { + listener.messageReceived(this, bytes); + } + } else { + socket.close(); + disconnectAll(); + break; + } + } catch (IOException | IllegalAccessException | InvocationTargetException e) { + logger.error(e.getMessage(), e); + disconnectAll(); + break; + } + } + }).start(); + } + + private void disconnectAll() { + for (Listener listener : listeners) { + try { + listener.disconnected(this); + } catch (InvocationTargetException | IllegalAccessException e) { + logger.error(e.getMessage(), e); + } + } + } + + @Override + public void close() throws IOException { + socket.close(); + } +} diff --git a/src/main/java/javagrinko/spring/tcp/TcpController.java b/src/main/java/javagrinko/spring/tcp/TcpController.java new file mode 100644 index 0000000..3ef8ee0 --- /dev/null +++ b/src/main/java/javagrinko/spring/tcp/TcpController.java @@ -0,0 +1,11 @@ +package javagrinko.spring.tcp; + +import org.springframework.stereotype.Component; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Component +@Retention(RetentionPolicy.RUNTIME) +public @interface TcpController { +} diff --git a/src/main/java/javagrinko/spring/tcp/TcpControllerBeanPostProcessor.java b/src/main/java/javagrinko/spring/tcp/TcpControllerBeanPostProcessor.java new file mode 100644 index 0000000..0919b7f --- /dev/null +++ b/src/main/java/javagrinko/spring/tcp/TcpControllerBeanPostProcessor.java @@ -0,0 +1,52 @@ +package javagrinko.spring.tcp; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.stereotype.Component; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +@Component +public class TcpControllerBeanPostProcessor implements BeanPostProcessor { + private Map cache = new HashMap<>(); + + @Autowired + private Server server; + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + Class beanClass = bean.getClass(); + if (bean instanceof TcpHandler) { + cache.put(beanName, beanClass); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (cache.containsKey(beanName)) { + TcpHandler tcpHandler = (TcpHandler) bean; + server.addListener(new Connection.Listener() { + @Override + public void messageReceived(Connection connection, byte[] bytes) + throws InvocationTargetException, IllegalAccessException { + tcpHandler.receiveData(connection, bytes); + } + + @Override + public void connected(Connection connection) throws InvocationTargetException, IllegalAccessException { + tcpHandler.connectEvent(connection); + } + + @Override + public void disconnected(Connection connection) throws InvocationTargetException, IllegalAccessException { + tcpHandler.disconnectEvent(connection); + } + }); + } + return bean; + } +} diff --git a/src/main/java/javagrinko/spring/tcp/TcpHandler.java b/src/main/java/javagrinko/spring/tcp/TcpHandler.java new file mode 100644 index 0000000..2e7d47d --- /dev/null +++ b/src/main/java/javagrinko/spring/tcp/TcpHandler.java @@ -0,0 +1,7 @@ +package javagrinko.spring.tcp; + +public interface TcpHandler { + void receiveData(Connection connection, byte[] data); + void connectEvent(Connection connection); + void disconnectEvent(Connection connection); +} \ No newline at end of file diff --git a/src/main/java/javagrinko/spring/tcp/TcpServer.java b/src/main/java/javagrinko/spring/tcp/TcpServer.java new file mode 100644 index 0000000..043c398 --- /dev/null +++ b/src/main/java/javagrinko/spring/tcp/TcpServer.java @@ -0,0 +1,108 @@ +package javagrinko.spring.tcp; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +@Component +public class TcpServer implements Server, Connection.Listener { + private static Log logger = LogFactory.getLog(TcpServer.class); + + private ServerSocket serverSocket; + private volatile boolean isStop; + private List connections = new CopyOnWriteArrayList<>(); + private List listeners = new CopyOnWriteArrayList<>(); + + public void setPort(Integer port) { + try { + serverSocket = new ServerSocket(port); + logger.info("Server start at port " + port); + } catch (IOException e) { + logger.error("Port " + port + " busy.", e); + } + } + + @Override + public int getConnectionsCount() { + return connections.size(); + } + + @Override + public void start() { + new Thread(() -> { + while (!isStop) { + try { + Socket socket = serverSocket.accept(); + if (socket.isConnected()) { + TcpConnection tcpConnection = new TcpConnection(socket); + tcpConnection.start(); + tcpConnection.addListener(this); + connected(tcpConnection); + } + } catch (IOException | IllegalAccessException | InvocationTargetException e) { + logger.error(e.getMessage(), e); + } + } + }).start(); + } + + @Override + public void stop() { + isStop = true; + } + + @Override + public List getConnections() { + return connections; + } + + @Override + public void addListener(Connection.Listener listener) { + listeners.add(listener); + } + + @Override + public void messageReceived(Connection connection, byte[] bytes) + throws InvocationTargetException, IllegalAccessException { + if (logger.isTraceEnabled()) { + logger.trace("Received message from " + connection.getAddress().getCanonicalHostName()); + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X ", b)); + } + logger.trace(" <== " + sb.toString()); + } + for (Connection.Listener listener : listeners) { + listener.messageReceived(connection, bytes); + } + } + + @Override + public void connected(Connection connection) + throws InvocationTargetException, IllegalAccessException { + logger.info("New connection! Ip: " + connection.getAddress().getCanonicalHostName() + "."); + connections.add(connection); + logger.info("Current connections count: " + connections.size()); + for (Connection.Listener listener : listeners) { + listener.connected(connection); + } + } + + @Override + public void disconnected(Connection connection) + throws InvocationTargetException, IllegalAccessException { + logger.info("Disconnect! Ip: " + connection.getAddress().getCanonicalHostName() + "."); + connections.remove(connection); + logger.info("Current connections count: " + connections.size()); + for (Connection.Listener listener : listeners) { + listener.disconnected(connection); + } + } +} diff --git a/src/main/java/javagrinko/spring/tcp/TcpServerAutoStarterApplicationListener.java b/src/main/java/javagrinko/spring/tcp/TcpServerAutoStarterApplicationListener.java new file mode 100644 index 0000000..95606a8 --- /dev/null +++ b/src/main/java/javagrinko/spring/tcp/TcpServerAutoStarterApplicationListener.java @@ -0,0 +1,26 @@ +package javagrinko.spring.tcp; + +import javagrinko.spring.starter.TcpServerProperties; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +@Component +public class TcpServerAutoStarterApplicationListener implements ApplicationListener { + + @Autowired + private TcpServerProperties properties; + + @Autowired + private Server server; + + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + boolean autoStart = properties.getAutoStart(); + if (autoStart){ + server.setPort(properties.getPort()); + server.start(); + } + } +}