diff --git a/core/pom.xml b/core/pom.xml index 23864e0..6a8de2f 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -17,4 +17,12 @@ 17 + + + + com.google.code.gson + gson + 2.9.0 + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index e6af771..97bfde5 100644 --- a/pom.xml +++ b/pom.xml @@ -20,15 +20,6 @@ UTF-8 - - - - com.google.code.gson - gson - 2.9.0 - - - diff --git a/server/src/main/java/nl/andrewl/starship_arena/server/ClientManager.java b/server/src/main/java/nl/andrewl/starship_arena/server/ClientManager.java new file mode 100644 index 0000000..e2b9a42 --- /dev/null +++ b/server/src/main/java/nl/andrewl/starship_arena/server/ClientManager.java @@ -0,0 +1,31 @@ +package nl.andrewl.starship_arena.server; + +import nl.andrewl.starship_arena.server.model.Arena; + +import java.io.IOException; +import java.net.Socket; + +public class ClientManager extends Thread { + private final Arena arena; + private final Socket clientSocket; + + public ClientManager(Arena arena, Socket clientSocket) { + this.arena = arena; + this.clientSocket = clientSocket; + } + + public void shutdown() { + try { + clientSocket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void run() { + while (clientSocket.isConnected() && !clientSocket.isClosed()) { + + } + } +} diff --git a/server/src/main/java/nl/andrewl/starship_arena/server/StarshipArenaServer.java b/server/src/main/java/nl/andrewl/starship_arena/server/StarshipArenaServer.java index 1779b65..eafb506 100644 --- a/server/src/main/java/nl/andrewl/starship_arena/server/StarshipArenaServer.java +++ b/server/src/main/java/nl/andrewl/starship_arena/server/StarshipArenaServer.java @@ -1,23 +1,90 @@ package nl.andrewl.starship_arena.server; +import nl.andrewl.starship_arena.server.model.Arena; import nl.andrewl.starship_arena.server.servlet.ArenasServlet; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.DataInputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; public class StarshipArenaServer { + private static final Logger logger = LoggerFactory.getLogger(StarshipArenaServer.class); + + private final Map arenas = new ConcurrentHashMap<>(); + private final ServerSocket serverSocket; + private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + public static void main(String[] args) throws Exception { + StarshipArenaServer server = new StarshipArenaServer(); + server.registerArena(new Arena()); + server.registerArena(new Arena("Andrew's Arena")); + Server jettyServer = new Server(8080); Connector connector = new ServerConnector(jettyServer); jettyServer.addConnector(connector); ServletContextHandler servletContext = new ServletContextHandler(); servletContext.setContextPath("/"); - servletContext.addServlet(new ServletHolder(new ArenasServlet()), "/arenas"); + servletContext.addServlet(new ServletHolder(new ArenasServlet(server)), "/arenas"); jettyServer.setHandler(servletContext); jettyServer.start(); + server.acceptConnections(); + } + + public StarshipArenaServer() throws IOException { + serverSocket = new ServerSocket(); + serverSocket.setReuseAddress(true); + } + + public List getArenas() { + List sortedArenas = new ArrayList<>(this.arenas.values()); + sortedArenas.sort(Comparator.comparing(Arena::getCreatedAt)); + return sortedArenas; + } + + public void registerArena(Arena a) { + arenas.put(a.getId(), a); + } + + public void acceptConnections() throws Exception { + serverSocket.bind(new InetSocketAddress("127.0.0.1", 8081)); + logger.info("Now accepting TCP connections."); + while (!serverSocket.isClosed()) { + Socket clientSocket = serverSocket.accept(); + logger.info("Client connected from {}", clientSocket.getRemoteSocketAddress()); + executor.submit(() -> { + try { + processIncomingConnection(clientSocket); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + } + + private void processIncomingConnection(Socket clientSocket) throws IOException { + var din = new DataInputStream(clientSocket.getInputStream()); + UUID arenaId = new UUID(din.readLong(), din.readLong()); + Arena arena = arenas.get(arenaId); + if (arena != null) { + var cm = new ClientManager(arenas.get(arenaId), clientSocket); + cm.start(); + } else { + clientSocket.close(); + } } } diff --git a/server/src/main/java/nl/andrewl/starship_arena/server/model/Arena.java b/server/src/main/java/nl/andrewl/starship_arena/server/model/Arena.java index 4a53efc..f00f160 100644 --- a/server/src/main/java/nl/andrewl/starship_arena/server/model/Arena.java +++ b/server/src/main/java/nl/andrewl/starship_arena/server/model/Arena.java @@ -1,4 +1,67 @@ package nl.andrewl.starship_arena.server.model; +import nl.andrewl.starship_arena.server.ClientManager; + +import java.time.Instant; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + public class Arena { + private final UUID id; + private final Instant createdAt; + private final String name; + + private ArenaStage currentStage = ArenaStage.STAGING; + + private final Map clients = new ConcurrentHashMap<>(); + + public Arena(String name) { + this.id = UUID.randomUUID(); + this.createdAt = Instant.now(); + this.name = name; + } + + public Arena() { + this("Unnamed Arena"); + } + + public UUID getId() { + return id; + } + + public Instant getCreatedAt() { + return createdAt; + } + + public String getName() { + return name; + } + + public ArenaStage getCurrentStage() { + return currentStage; + } + + public void registerClient(UUID id, ClientManager clientManager) { + if (clients.containsKey(id)) { + clientManager.shutdown(); + } else { + clients.put(id, clientManager); + } + } + + @Override + public boolean equals(Object o) { + return o == this || o instanceof Arena a && id.equals(a.id); + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public String toString() { + return id.toString(); + } } diff --git a/server/src/main/java/nl/andrewl/starship_arena/server/model/ArenaStage.java b/server/src/main/java/nl/andrewl/starship_arena/server/model/ArenaStage.java new file mode 100644 index 0000000..a72aab3 --- /dev/null +++ b/server/src/main/java/nl/andrewl/starship_arena/server/model/ArenaStage.java @@ -0,0 +1,7 @@ +package nl.andrewl.starship_arena.server.model; + +public enum ArenaStage { + STAGING, + BATTLE, + ANALYSIS +} diff --git a/server/src/main/java/nl/andrewl/starship_arena/server/servlet/ArenaRequest.java b/server/src/main/java/nl/andrewl/starship_arena/server/servlet/ArenaRequest.java new file mode 100644 index 0000000..f1438b1 --- /dev/null +++ b/server/src/main/java/nl/andrewl/starship_arena/server/servlet/ArenaRequest.java @@ -0,0 +1,5 @@ +package nl.andrewl.starship_arena.server.servlet; + +public class ArenaRequest { + public String name; +} diff --git a/server/src/main/java/nl/andrewl/starship_arena/server/servlet/ArenaResponse.java b/server/src/main/java/nl/andrewl/starship_arena/server/servlet/ArenaResponse.java new file mode 100644 index 0000000..54b2ed0 --- /dev/null +++ b/server/src/main/java/nl/andrewl/starship_arena/server/servlet/ArenaResponse.java @@ -0,0 +1,19 @@ +package nl.andrewl.starship_arena.server.servlet; + +import nl.andrewl.starship_arena.server.model.Arena; + +public record ArenaResponse( + String id, + String createdAt, + String name, + String currentStage +) { + public ArenaResponse(Arena a) { + this( + a.getId().toString(), + a.getCreatedAt().toString(), + a.getName(), + a.getCurrentStage().name() + ); + } +} diff --git a/server/src/main/java/nl/andrewl/starship_arena/server/servlet/ArenasServlet.java b/server/src/main/java/nl/andrewl/starship_arena/server/servlet/ArenasServlet.java index 5fdb694..5eb9b49 100644 --- a/server/src/main/java/nl/andrewl/starship_arena/server/servlet/ArenasServlet.java +++ b/server/src/main/java/nl/andrewl/starship_arena/server/servlet/ArenasServlet.java @@ -1,16 +1,51 @@ package nl.andrewl.starship_arena.server.servlet; -import jakarta.servlet.ServletException; +import com.google.gson.Gson; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import nl.andrewl.starship_arena.server.StarshipArenaServer; +import nl.andrewl.starship_arena.server.model.Arena; import java.io.IOException; +import java.io.InputStreamReader; +import java.util.List; public class ArenasServlet extends HttpServlet { + private static final Gson gson = new Gson(); + private final StarshipArenaServer server; + + public ArenasServlet(StarshipArenaServer server) { + this.server = server; + } + @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - super.doGet(req, resp); - // TODO: Return the list of available arenas. + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + List data = server.getArenas().stream() + .map(ArenaResponse::new).toList(); + resp.setStatus(HttpServletResponse.SC_OK); + resp.setContentType("application/json"); + resp.setCharacterEncoding("UTF-8"); + gson.toJson(data, resp.getWriter()); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { + if (!req.getContentType().equals("application/json")) resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Only JSON is allowed."); + ArenaRequest arenaRequest; + try { + arenaRequest = gson.fromJson(new InputStreamReader(req.getInputStream()), ArenaRequest.class); + } catch (JsonSyntaxException | JsonIOException e) { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Invalid JSON."); + return; + } + Arena arena = arenaRequest.name == null ? new Arena() : new Arena(arenaRequest.name); + server.registerArena(arena); + resp.setStatus(HttpServletResponse.SC_CREATED); + resp.setContentType("application/json"); + resp.setCharacterEncoding("UTF-8"); + gson.toJson(new ArenaResponse(arena), resp.getWriter()); } }