[update] Refactor Server class to streamline database connection handling and configuration loading. Replace HikariCP with direct SQLite connection for improved simplicity. Enhance logging for server address and port configuration. Update Google Drive integration methods and add functionality to save configuration changes. Clean up unused methods and ensure proper initialization of server components.

This commit is contained in:
Lê Thành Đạt committed 2025-08-02 05:12:27 +07:00
1 parent e87da4bc27
commit f6ec011107
3 files changed
+186 -109

No files matched your search

+182 -105
View File
@@ -2,8 +2,6 @@ package BsK.server;
import BsK.server.network.handler.ServerHandler;
import BsK.server.service.GoogleDriveServiceOAuth;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
@@ -25,32 +23,32 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Server {
private static int PORT;
// --- REFACTORED DATABASE CONNECTION ---
// The single, static connection pool (DataSource). This is thread-safe.
private static HikariDataSource dataSource;
// These static fields are still okay as they are configured once at startup.
public static GoogleDriveServiceOAuth googleDriveService;
private static boolean googleDriveEnabled = true;
private static String googleDriveRootFolderName = "BSK_Clinic_Patient_Files";
public static String imageDbPath = "img_db";
public static String checkupMediaBaseDir = "src/main/resources/image/checkup_media";
private static int PORT;
public static Connection connection;
public static GoogleDriveServiceOAuth googleDriveService;
private static boolean googleDriveEnabled = true; // Can be configured
private static String googleDriveRootFolderName = "BSK_Clinic_Patient_Files"; // Default
public static String imageDbPath = "img_db"; // Default
public static String checkupMediaBaseDir = "src/main/resources/image/checkup_media"; // Default
// Static initializer block to set up the server configuration and connection pool
static {
try {
// --- Load configuration ---
// Load configuration
Properties props = new Properties();
// First try to load from external config file
File externalConfig = new File("config/config.properties");
if (externalConfig.exists()) {
try (var input = Files.newInputStream(externalConfig.toPath())) {
@@ -58,6 +56,7 @@ public class Server {
log.info("Loaded configuration from external file: {}", externalConfig.getAbsolutePath());
}
} else {
// Fall back to internal config
try (InputStream input = Server.class.getClassLoader().getResourceAsStream("config.properties")) {
if (input == null) {
log.error("Unable to find config.properties");
@@ -67,108 +66,67 @@ public class Server {
log.info("Loaded configuration from internal resources");
}
}
// Get port configuration
String portStr = props.getProperty("server.port");
String addressStr = props.getProperty("server.address");
// Log address configuration
if (addressStr != null) {
log.info("Server address configured as: {}", addressStr);
} else {
log.info("No server address configured, clients will need to use the correct address to connect");
}
// Get port configuration
if (portStr != null) {
PORT = Integer.parseInt(portStr);
log.info("Using configured port: {}", PORT);
} else {
PORT = 1999;
log.info("Using default port: {}", PORT);
}
PORT = Integer.parseInt(props.getProperty("server.port", "1999"));
log.info("Using port: {}", PORT);
// Load other properties
googleDriveEnabled = Boolean.parseBoolean(props.getProperty("google.drive.enabled", "true"));
googleDriveRootFolderName = props.getProperty("google.drive.root.folder", "BSK_Clinic_Patient_Files").trim();
imageDbPath = props.getProperty("storage.image_db_path", "img_db").trim();
checkupMediaBaseDir = props.getProperty("storage.checkup_media_base_dir", "src/main/resources/image/checkup_media").trim();
log.info("Google Drive enabled: {}", googleDriveEnabled);
// Get Google Drive configuration
String driveEnabledStr = props.getProperty("google.drive.enabled");
if (driveEnabledStr != null) {
googleDriveEnabled = Boolean.parseBoolean(driveEnabledStr);
}
String driveRootFolderStr = props.getProperty("google.drive.root.folder");
if (driveRootFolderStr != null && !driveRootFolderStr.trim().isEmpty()) {
googleDriveRootFolderName = driveRootFolderStr.trim();
}
log.info("Google Drive integration enabled: {}", googleDriveEnabled);
log.info("Google Drive root folder: {}", googleDriveRootFolderName);
// Get Storage paths configuration
String imageDbPathStr = props.getProperty("storage.image_db_path");
if (imageDbPathStr != null && !imageDbPathStr.trim().isEmpty()) {
imageDbPath = imageDbPathStr.trim();
}
String checkupMediaBaseDirStr = props.getProperty("storage.checkup_media_base_dir");
if (checkupMediaBaseDirStr != null && !checkupMediaBaseDirStr.trim().isEmpty()) {
checkupMediaBaseDir = checkupMediaBaseDirStr.trim();
}
log.info("Image DB path: {}", imageDbPath);
log.info("Checkup media base dir: {}", checkupMediaBaseDir);
// --- DATABASE POOL INITIALIZATION ---
// Directly use the provided database path
String dbPath = "src/main/resources/database/BSK.db";
// 1. Configure HikariCP
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:sqlite:" + dbPath);
config.setConnectionTestQuery("SELECT 1");
config.setMaximumPoolSize(10); // Max 10 concurrent connections, perfect for your use case
config.setPoolName("BsK-DB-Pool");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
// 2. Create the DataSource (the pool itself)
dataSource = new HikariDataSource(config);
log.info("✅ Database connection pool initialized successfully for {}", dbPath);
connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath);
// Initialize Google Drive service if enabled
initializeGoogleDriveService();
} catch (Exception e) {
log.error("❌ Failed to initialize server", e);
throw new RuntimeException("Failed to initialize server", e);
}
}
/**
* This is the new, safe way for handlers to get a database connection.
* @return A database connection from the pool.
* @throws SQLException if a connection cannot be obtained.
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void main(String[] args) throws InterruptedException {
log.info("\n____ _____ _ __\n| _ \\ / ____| |/ \n| |_) | (___ | ' / \n| _ < \\___ \\| <\n| |_) |____) | . \\\n|____/|_____/|_|\\_\n");
ServerDashboard dashboard = ServerDashboard.getInstance();
dashboard.setVisible(true);
dashboard.updateStatus("Starting...", Color.ORANGE);
dashboard.updatePort(PORT);
dashboard.updateGoogleDriveStatus(isGoogleDriveConnected(), isGoogleDriveConnected() ? "Connected" : "Disconnected");
EventLoopGroup parentGroup = Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup();
EventLoopGroup childGroup = Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap()
.group(parentGroup, childGroup)
.channel(Epoll.isAvailable() ? EpollServerSocketChannel.class : NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(PORT))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new IdleStateHandler(60 * 30, 0, 0, TimeUnit.SECONDS))
.addLast(new HttpServerCodec())
.addLast(new ChunkedWriteHandler())
.addLast(new HttpObjectAggregator(50 * 1024 * 1024))
.addLast(new WebSocketServerProtocolHandler("/", null, true, 50 * 1024 * 1024))
.addLast(new ServerHandler()); // A new handler for each client
}
});
ChannelFuture f = bootstrap.bind().sync();
log.info("Server started on port {}", PORT);
dashboard.updateStatus("Running", Color.GREEN);
dashboard.addLog("Server started successfully on port " + PORT);
f.channel().closeFuture().sync();
} catch (Exception e) {
dashboard.updateStatus("Error", Color.RED);
dashboard.addLog("Server error: " + e.getMessage());
log.error("Server crashed", e);
} finally {
dashboard.updateStatus("Shutting down", Color.ORANGE);
dashboard.addLog("Server shutting down...");
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
if (dataSource != null) {
dataSource.close(); // Close the connection pool on shutdown
log.info("Database connection pool closed.");
}
}
}
// --- Google Drive and other helper methods ---
private static void initializeGoogleDriveService() {
if (!googleDriveEnabled) {
log.info("Google Drive integration is disabled");
@@ -181,6 +139,7 @@ public class Server {
googleDriveService = new GoogleDriveServiceOAuth(googleDriveRootFolderName);
log.info("✅ Google Drive OAuth service initialized successfully");
// Update dashboard if it exists
if (ServerDashboard.getInstance() != null) {
ServerDashboard.getInstance().updateGoogleDriveStatus(true, "Connected");
}
@@ -188,31 +147,48 @@ public class Server {
} catch (Exception e) {
log.error("❌ Failed to initialize Google Drive OAuth service: {}", e.getMessage());
log.info("💡 Server will continue without Google Drive integration");
log.info("🔧 To fix this, ensure google-oauth-credentials.json is properly configured");
googleDriveService = null;
// Update dashboard if it exists
if (ServerDashboard.getInstance() != null) {
ServerDashboard.getInstance().updateGoogleDriveStatus(false, "Failed: " + e.getMessage());
}
}
}
/**
* Manually retry Google Drive connection
*/
public static void retryGoogleDriveConnection() {
log.info("🔄 Retrying Google Drive connection...");
initializeGoogleDriveService();
}
/**
* Check if Google Drive is connected
*/
public static boolean isGoogleDriveConnected() {
return googleDriveService != null;
}
/**
* Get Google Drive service instance
*/
public static GoogleDriveServiceOAuth getGoogleDriveService() {
return googleDriveService;
}
/**
* Get the current Google Drive root folder name
*/
public static String getGoogleDriveRootFolderName() {
return googleDriveRootFolderName;
}
/**
* Update Google Drive root folder name and reinitialize service
*/
public static void updateGoogleDriveRootFolder(String newRootFolderName) throws Exception {
if (newRootFolderName == null || newRootFolderName.trim().isEmpty()) {
throw new IllegalArgumentException("Root folder name cannot be empty");
@@ -221,17 +197,23 @@ public class Server {
googleDriveRootFolderName = newRootFolderName.trim();
log.info("🔄 Updating Google Drive root folder to: {}", googleDriveRootFolderName);
// Save to configuration file
saveGoogleDriveRootFolderToConfig(googleDriveRootFolderName);
// Reinitialize Google Drive service with new root folder
if (googleDriveEnabled) {
initializeGoogleDriveService();
}
}
/**
* Save Google Drive root folder name to config file
*/
private static void saveGoogleDriveRootFolderToConfig(String rootFolderName) {
try {
Properties props = new Properties();
// Load existing properties
File externalConfig = new File("config/config.properties");
if (externalConfig.exists()) {
try (var input = Files.newInputStream(externalConfig.toPath())) {
@@ -245,13 +227,16 @@ public class Server {
}
}
// Update the root folder property
props.setProperty("google.drive.root.folder", rootFolderName);
// Create config directory if it doesn't exist
File configDir = new File("config");
if (!configDir.exists()) {
configDir.mkdirs();
}
// Save to external config file
try (var output = Files.newOutputStream(externalConfig.toPath())) {
props.store(output, "BSK Server Configuration - Updated by Dashboard");
log.info("💾 Saved Google Drive root folder setting to config file");
@@ -262,6 +247,96 @@ public class Server {
}
}
private static String extractDatabaseFile() throws IOException {
throw new UnsupportedOperationException("extractDatabaseFile is not used anymore. Database is read directly from the specified path.");
}
public static Statement statement;
static {
try {
statement = connection.createStatement();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public Server() throws SQLException {
}
public static void main(String[] args) throws InterruptedException, SQLException {
log.info("\n____ _____ _ __\n| _ \\ / ____| |/\n| |_) | (___ | ' /\n| _ < \\___ \\| <\n| |_) |____) | . \\\n|____/|_____/|_|\\_\n");
// Initialize and show server dashboard
ServerDashboard dashboard = ServerDashboard.getInstance();
dashboard.setVisible(true);
dashboard.updateStatus("Starting...", Color.ORANGE);
dashboard.updatePort(PORT);
// Update Google Drive status on dashboard
dashboard.updateGoogleDriveStatus(isGoogleDriveConnected(),
isGoogleDriveConnected() ? "Connected" : "Disconnected");
EventLoopGroup parentGroup =
Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup();
EventLoopGroup childGroup =
Epoll.isAvailable() ? new EpollEventLoopGroup() : new NioEventLoopGroup();
try {
ServerBootstrap bootstrap =
new ServerBootstrap()
.group(parentGroup, childGroup)
.channel(
Epoll.isAvailable()
? EpollServerSocketChannel.class
: NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(PORT))
.childHandler(
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new IdleStateHandler(60 * 30, 0, 0, TimeUnit.SECONDS))
.addLast(new HttpServerCodec())
.addLast(new ChunkedWriteHandler())
.addLast(new HttpObjectAggregator(50 * 1024 * 1024))
.addLast(new WebSocketServerProtocolHandler("/", null, true, 50 * 1024 * 1024))
.addLast(new ServerHandler());
}
});
ChannelFuture f = bootstrap.bind().sync();
log.info("Server started on port {}", PORT);
log.info("To connect, clients should use:");
log.info(" - If on same machine: ws://localhost:{} or ws://127.0.0.1:{}", PORT, PORT);
log.info(" - If on network: ws://<this-computer's-ip>:{}", PORT);
// Update dashboard status to running
dashboard.updateStatus("Running", Color.GREEN);
dashboard.addLog("Server started successfully on port " + PORT);
if (isGoogleDriveConnected()) {
dashboard.addLog("Google Drive integration: Connected ✅");
} else {
dashboard.addLog("Google Drive integration: Disconnected ❌");
}
f.channel().closeFuture().sync();
} catch (Exception e) {
dashboard.updateStatus("Error", Color.RED);
dashboard.addLog("Server error: " + e.getMessage());
throw e;
} finally {
dashboard.updateStatus("Shutting down", Color.ORANGE);
dashboard.addLog("Server shutting down...");
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
/**
* Initiates the backup of the main database file to Google Drive.
* This is intended to be called from the ServerDashboard.
* @throws IOException if the file cannot be found or the upload fails.
*/
public static void backupDatabaseToDrive() throws IOException {
ServerDashboard dashboard = ServerDashboard.getInstance();
@@ -270,6 +345,7 @@ public class Server {
throw new IOException("Google Drive service is not available.");
}
// Define the path to the database
String dbPath = "src/main/resources/database/BSK.db";
java.io.File dbFile = new java.io.File(dbPath);
@@ -278,6 +354,7 @@ public class Server {
throw new IOException("Database file not found: " + dbPath);
}
// Call the service to perform the backup
googleDriveService.backupDatabaseFile(dbFile);
}
}
}
}
@@ -58,4 +58,4 @@ public class User {
SessionManager.updateUserRole(channel.id().asLongText(), this.role.toString(), this.userId);
log.info("User session {} has been de-authenticated.", sessionId);
}
}
}
@@ -596,7 +596,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
// --- 2. CHÈN VÀO BẢNG CHECKUP VÀ LẤY LẠI CHECKUP_ID ---
String checkupSql = "INSERT INTO Checkup (customer_id, doctor_id, checkup_type, status, queue_number) VALUES (?, ?, ?, ?, ?)";
try (PreparedStatement checkupStmt = Server.connection.prepareStatement(checkupSql, Statement.RETURN_GENERATED_KEYS)) {
try (PreparedStatement checkupStmt = Server.connection.prepareStatement(checkupSql, PreparedStatement.RETURN_GENERATED_KEYS)) {
checkupStmt.setInt(1, addCheckupRequest.getCustomerId());
checkupStmt.setInt(2, addCheckupRequest.getDoctorId());
checkupStmt.setString(3, addCheckupRequest.getCheckupType());
@@ -617,7 +617,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
// --- 3. CHÈN VÀO BẢNG MEDICINEORDER VÀ LẤY LẠI PRESCRIPTION_ID ---
int generatedPrescriptionId = 0;
String medOrderSql = "INSERT INTO MedicineOrder (checkup_id, customer_id, processed_by) VALUES (?, ?, ?)";
try (PreparedStatement medOrderStmt = Server.connection.prepareStatement(medOrderSql, Statement.RETURN_GENERATED_KEYS)) {
try (PreparedStatement medOrderStmt = Server.connection.prepareStatement(medOrderSql, PreparedStatement.RETURN_GENERATED_KEYS)) {
medOrderStmt.setInt(1, generatedCheckupId); // Sử dụng ID đã lấy được
medOrderStmt.setInt(2, addCheckupRequest.getCustomerId());
medOrderStmt.setInt(3, addCheckupRequest.getProcessedById());
@@ -2274,4 +2274,4 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
}
}
}
}