diff --git a/java-api/src/main/java/local/processmonitor/api/Main.java b/java-api/src/main/java/local/processmonitor/api/Main.java deleted file mode 100644 index ff434a4..0000000 --- a/java-api/src/main/java/local/processmonitor/api/Main.java +++ /dev/null @@ -1,212 +0,0 @@ -package local.processmonitor.api; - -import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; -import com.sun.net.httpserver.Headers; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpServer; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.time.Instant; -import java.time.format.DateTimeParseException; -import java.util.Collections; -import java.util.List; -import java.util.Properties; - -public class Main { - private static final Gson GSON = new Gson(); - - public static void main(String[] args) throws Exception { - AppConfig config = AppConfig.load(); - Database database = new Database(config); - HttpServer server = HttpServer.create(new InetSocketAddress(config.listenHost, config.listenPort), 0); - server.createContext(config.apiPath, new HeartbeatHandler(database)); - server.setExecutor(null); - server.start(); - - log("Listening on http://" + config.listenHost + ":" + config.listenPort + config.apiPath); - } - - private static final class HeartbeatHandler implements HttpHandler { - private final Database database; - - private HeartbeatHandler(Database database) { - this.database = database; - } - - @Override - public void handle(HttpExchange exchange) throws IOException { - try { - if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) { - sendJson(exchange, 405, "{\"error\":\"method_not_allowed\"}"); - return; - } - - Headers headers = exchange.getRequestHeaders(); - String contentType = headers.getFirst("Content-Type"); - if (contentType == null || !contentType.toLowerCase().contains("application/json")) { - sendJson(exchange, 415, "{\"error\":\"unsupported_media_type\"}"); - return; - } - - String body = readBody(exchange.getRequestBody()); - log("Incoming heartbeat request: " + body); - HeartbeatRequest request = HeartbeatRequest.parse(body); - int insertedRows = database.saveHeartbeat(request); - log("Stored heartbeat for machine " + request.machineName + ", inserted rows: " + insertedRows); - sendJson(exchange, 200, String.format("{\"ok\":true,\"inserted\":%d}", insertedRows)); - } catch (IllegalArgumentException ex) { - log("Request validation failed: " + ex.getMessage()); - sendJson(exchange, 400, "{\"error\":\"" + escapeJson(ex.getMessage()) + "\"}"); - } catch (Exception ex) { - ex.printStackTrace(); - log("Internal server error: " + ex.getMessage()); - sendJson(exchange, 500, "{\"error\":\"internal_error\"}"); - } - } - - private static String readBody(InputStream inputStream) throws IOException { - return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); - } - - private static void sendJson(HttpExchange exchange, int statusCode, String body) throws IOException { - byte[] bytes = body.getBytes(StandardCharsets.UTF_8); - exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8"); - exchange.sendResponseHeaders(statusCode, bytes.length); - try (OutputStream outputStream = exchange.getResponseBody()) { - outputStream.write(bytes); - } - } - } - - private static final class AppConfig { - private final String listenHost; - private final int listenPort; - private final String apiPath; - private final String jdbcUrl; - private final String dbUser; - private final String dbPassword; - - private AppConfig(String listenHost, int listenPort, String apiPath, String jdbcUrl, String dbUser, String dbPassword) { - this.listenHost = listenHost; - this.listenPort = listenPort; - this.apiPath = apiPath; - this.jdbcUrl = jdbcUrl; - this.dbUser = dbUser; - this.dbPassword = dbPassword; - } - - private static AppConfig load() throws IOException { - Properties properties = new Properties(); - try (InputStream inputStream = Main.class.getClassLoader().getResourceAsStream("application.properties")) { - if (inputStream == null) { - throw new IOException("Missing application.properties"); - } - properties.load(inputStream); - } - - String listenHost = properties.getProperty("server.host", "0.0.0.0"); - int listenPort = Integer.parseInt(properties.getProperty("server.port", "8080")); - String apiPath = properties.getProperty("server.path", "/hb/api"); - String jdbcUrl = required(properties, "db.url"); - String dbUser = required(properties, "db.user"); - String dbPassword = required(properties, "db.password"); - return new AppConfig(listenHost, listenPort, apiPath, jdbcUrl, dbUser, dbPassword); - } - - private static String required(Properties properties, String key) { - String value = properties.getProperty(key); - if (value == null || value.isBlank()) { - throw new IllegalArgumentException("Missing config key: " + key); - } - return value.trim(); - } - } - - private static final class Database { - private final AppConfig config; - - private Database(AppConfig config) { - this.config = config; - } - - private int saveHeartbeat(HeartbeatRequest request) throws SQLException { - List processes = request.processes.isEmpty() ? Collections.singletonList(null) : request.processes; - String sql = "INSERT INTO process_heartbeat (machine_name, status, detected_at, process_name) VALUES (?, ?, ?, ?)"; - - try (Connection connection = DriverManager.getConnection(config.jdbcUrl, config.dbUser, config.dbPassword); - PreparedStatement statement = connection.prepareStatement(sql)) { - int inserted = 0; - for (String processName : processes) { - statement.setString(1, request.machineName); - statement.setString(2, request.status); - statement.setTimestamp(3, Timestamp.from(request.detectedAt)); - statement.setString(4, processName); - inserted += statement.executeUpdate(); - } - return inserted; - } - } - } - - private static final class HeartbeatRequest { - private String machine_name; - private String status; - private String detected_at; - private List processes; - - private transient String machineName; - private transient Instant detectedAt; - - private static HeartbeatRequest parse(String json) { - try { - HeartbeatRequest request = GSON.fromJson(json, HeartbeatRequest.class); - if (request == null) { - throw new IllegalArgumentException("Empty request body"); - } - request.validate(); - return request; - } catch (JsonSyntaxException ex) { - throw new IllegalArgumentException("Invalid JSON"); - } - } - - private void validate() { - machineName = required(machine_name, "machine_name"); - status = required(status, "status"); - try { - detectedAt = Instant.parse(required(detected_at, "detected_at")); - } catch (DateTimeParseException ex) { - throw new IllegalArgumentException("Invalid detected_at"); - } - if (processes == null) { - processes = Collections.emptyList(); - } - } - - private static String required(String value, String fieldName) { - if (value == null || value.isBlank()) { - throw new IllegalArgumentException("Missing field: " + fieldName); - } - return value.trim(); - } - } - - private static void log(String message) { - System.out.printf("[%s] %s%n", Instant.now(), message); - } - - private static String escapeJson(String value) { - return value.replace("\\", "\\\\").replace("\"", "\\\""); - } -}