DBusHandlerConfiguration.java

/*
 * SPDX-FileCopyrightText: 2023-2025 Lucimber UG
 * SPDX-License-Identifier: Apache-2.0
 */
package com.lucimber.dbus.netty;

import com.lucimber.dbus.netty.sasl.SaslAuthenticationHandler;
import com.lucimber.dbus.netty.sasl.SaslCodec;
import com.lucimber.dbus.netty.sasl.SaslInitiationHandler;
import com.lucimber.dbus.util.LoggerUtils;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.Promise;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Centralized configuration for D-Bus pipeline handlers. This class maintains the definitive order
 * and creation logic for all handlers, ensuring perfect synchronization between initial setup and
 * reconnection scenarios.
 */
public final class DBusHandlerConfiguration {

    private static final Logger LOGGER = LoggerFactory.getLogger(DBusHandlerConfiguration.class);

    /**
     * Defines the complete handler pipeline order and creation logic. SASL handlers are added
     * first, followed by D-Bus protocol handlers.
     */
    private static Map<String, Supplier<ChannelHandler>> createHandlerMap(
            Promise<Void> connectPromise, RealityCheckpoint appLogicHandler) {

        Map<String, Supplier<ChannelHandler>> handlers = new LinkedHashMap<>();

        // SASL handlers (added first, removed after SASL completion)
        handlers.put(DBusHandlerNames.SASL_INITIATION_HANDLER, SaslInitiationHandler::new);
        handlers.put(DBusHandlerNames.SASL_CODEC, SaslCodec::new);
        handlers.put(DBusHandlerNames.SASL_AUTHENTICATION_HANDLER, SaslAuthenticationHandler::new);

        // D-Bus protocol handlers (permanent)
        handlers.put(
                DBusHandlerNames.NETTY_BYTE_LOGGER,
                () -> new LoggingHandler(LoggerUtils.TRANSPORT.getName(), LogLevel.DEBUG));
        handlers.put(DBusHandlerNames.FRAME_ENCODER, FrameEncoder::new);
        handlers.put(DBusHandlerNames.OUTBOUND_MESSAGE_ENCODER, OutboundMessageEncoder::new);
        handlers.put(DBusHandlerNames.FRAME_DECODER, FrameDecoder::new);
        handlers.put(DBusHandlerNames.INBOUND_MESSAGE_DECODER, InboundMessageDecoder::new);
        handlers.put(DBusHandlerNames.DBUS_MANDATORY_NAME_HANDLER, DBusMandatoryNameHandler::new);
        handlers.put(
                DBusHandlerNames.CONNECTION_COMPLETION_HANDLER,
                () -> new ConnectionCompletionHandler(connectPromise));

        // Reconnection management handler
        handlers.put(
                "ReconnectionHandlerManager", () -> new ReconnectionHandlerManager(connectPromise));

        // Application logic handler (always last)
        if (appLogicHandler != null) {
            handlers.put("RealityCheckpoint", () -> appLogicHandler);
        }

        return handlers;
    }

    /**
     * Initializes the complete pipeline in the correct order.
     *
     * @param pipeline the channel pipeline to configure
     * @param connectPromise the promise to complete when connection is established
     * @param appLogicHandler the application logic handler
     */
    public static void initializePipeline(
            ChannelPipeline pipeline,
            Promise<Void> connectPromise,
            RealityCheckpoint appLogicHandler) {
        LOGGER.debug(
                LoggerUtils.HANDLER_LIFECYCLE,
                "Initializing D-Bus pipeline with centralized configuration");

        Map<String, Supplier<ChannelHandler>> handlers =
                createHandlerMap(connectPromise, appLogicHandler);

        for (Map.Entry<String, Supplier<ChannelHandler>> entry : handlers.entrySet()) {
            String name = entry.getKey();
            ChannelHandler handler = entry.getValue().get();
            pipeline.addLast(name, handler);
            LOGGER.debug(LoggerUtils.HANDLER_LIFECYCLE, "Added handler: {}", name);
        }
    }

    /**
     * Gets the ordered list of SASL handler names that need to be re-added during reconnection.
     *
     * @return ordered map of SASL handler names to their creation suppliers
     */
    public static Map<String, Supplier<ChannelHandler>> getSaslHandlers() {
        Map<String, Supplier<ChannelHandler>> saslHandlers = new LinkedHashMap<>();
        saslHandlers.put(DBusHandlerNames.SASL_INITIATION_HANDLER, SaslInitiationHandler::new);
        saslHandlers.put(DBusHandlerNames.SASL_CODEC, SaslCodec::new);
        saslHandlers.put(
                DBusHandlerNames.SASL_AUTHENTICATION_HANDLER, SaslAuthenticationHandler::new);
        return saslHandlers;
    }

    /**
     * Gets the ordered list of all handler names in the pipeline.
     *
     * @return ordered list of handler names
     */
    public static String[] getHandlerOrder() {
        return new String[] {
            DBusHandlerNames.SASL_INITIATION_HANDLER,
            DBusHandlerNames.SASL_CODEC,
            DBusHandlerNames.SASL_AUTHENTICATION_HANDLER,
            DBusHandlerNames.NETTY_BYTE_LOGGER,
            DBusHandlerNames.FRAME_ENCODER,
            DBusHandlerNames.OUTBOUND_MESSAGE_ENCODER,
            DBusHandlerNames.FRAME_DECODER,
            DBusHandlerNames.INBOUND_MESSAGE_DECODER,
            DBusHandlerNames.DBUS_MANDATORY_NAME_HANDLER,
            DBusHandlerNames.CONNECTION_COMPLETION_HANDLER,
            "ReconnectionHandlerManager",
            "RealityCheckpoint"
        };
    }

    /**
     * Finds the first existing D-Bus handler in the pipeline to use as an insertion point.
     *
     * @param pipeline the channel pipeline to search
     * @return the name of the first D-Bus handler found, or null if none found
     */
    public static String findFirstDbusHandler(ChannelPipeline pipeline) {
        String[] dbusHandlers = {
            DBusHandlerNames.NETTY_BYTE_LOGGER,
            DBusHandlerNames.FRAME_ENCODER,
            DBusHandlerNames.OUTBOUND_MESSAGE_ENCODER,
            DBusHandlerNames.FRAME_DECODER,
            DBusHandlerNames.INBOUND_MESSAGE_DECODER,
            DBusHandlerNames.DBUS_MANDATORY_NAME_HANDLER,
            DBusHandlerNames.CONNECTION_COMPLETION_HANDLER
        };

        for (String handlerName : dbusHandlers) {
            if (pipeline.get(handlerName) != null) {
                return handlerName;
            }
        }

        return null;
    }

    private DBusHandlerConfiguration() {
        // Utility class - no instances
    }
}