WriteOperationListener.java

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

import com.lucimber.dbus.util.LoggerUtils;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import java.util.Objects;
import java.util.function.Consumer;
import org.slf4j.Logger;

/**
 * A {@link GenericFutureListener} that provides consistent logging for write operations and
 * optionally chains custom completion logic.
 *
 * <p>This listener automatically logs the outcome of write operations using the {@link
 * LoggerUtils#TRANSPORT} marker with appropriate log levels:
 *
 * <ul>
 *   <li><strong>DEBUG</strong> - Successful operations
 *   <li><strong>ERROR</strong> - Failed operations with exception details
 *   <li><strong>WARN</strong> - Cancelled operations
 * </ul>
 *
 * <p>After logging, the listener can optionally execute custom completion logic via the provided
 * {@link Consumer} function.
 *
 * <h2>Usage Examples</h2>
 *
 * <p><strong>Basic logging only:</strong>
 *
 * <pre>{@code
 * ctx.writeAndFlush(message)
 *    .addListener(new WriteOperationListener<>(LOGGER));
 * }</pre>
 *
 * <p><strong>With custom completion logic:</strong>
 *
 * <pre>{@code
 * ctx.writeAndFlush(message)
 *    .addListener(new WriteOperationListener<>(LOGGER, future -> {
 *        if (future.isSuccess()) {
 *            // Handle successful write
 *            processSuccessfulWrite();
 *        } else {
 *            // Handle failed write
 *            handleWriteFailure(future.cause());
 *        }
 *    }));
 * }</pre>
 *
 * <p><strong>SASL authentication example:</strong>
 *
 * <pre>{@code
 * ctx.writeAndFlush(authMessage)
 *    .addListener(new WriteOperationListener<>(LOGGER, future -> {
 *        if (future.isSuccess()) {
 *            currentState = State.AWAITING_RESPONSE;
 *            startResponseTimeout(ctx);
 *        } else {
 *            failAuthentication(ctx, "Failed to send auth message");
 *        }
 *    }));
 * }</pre>
 *
 * <p>This class is thread-safe and can be used across multiple channels and handlers. The logger
 * instance is not required to be static final, making it suitable for use in various contexts where
 * different loggers may be needed.
 *
 * @param <T> the type of future being listened to, must extend {@link Future}
 * @see GenericFutureListener
 * @see LoggerUtils#TRANSPORT
 * @since 1.0
 */
@SuppressWarnings("PMD.LoggerIsNotStaticFinal")
public final class WriteOperationListener<T extends Future<?>> implements GenericFutureListener<T> {

    private final Consumer<T> futureConsumer;
    private final Logger logger;

    /**
     * Creates a new WriteOperationListener that only performs logging.
     *
     * <p>This constructor creates a listener that will log the outcome of write operations but will
     * not execute any custom completion logic.
     *
     * @param logger the logger to use for logging write operation outcomes; must not be null
     * @throws NullPointerException if logger is null
     */
    public WriteOperationListener(Logger logger) {
        this(logger, null);
    }

    /**
     * Creates a new WriteOperationListener with custom completion logic.
     *
     * <p>This constructor creates a listener that will log the outcome of write operations and then
     * execute the provided custom completion logic.
     *
     * @param logger the logger to use for logging write operation outcomes; must not be null
     * @param futureConsumer optional consumer to execute custom completion logic; may be null
     * @throws NullPointerException if logger is null
     */
    public WriteOperationListener(Logger logger, Consumer<T> futureConsumer) {
        this.logger = Objects.requireNonNull(logger);
        this.futureConsumer = futureConsumer;
    }

    /**
     * Called when the write operation completes, regardless of success or failure.
     *
     * <p>This method first logs the outcome of the write operation using the {@link
     * LoggerUtils#TRANSPORT} marker:
     *
     * <ul>
     *   <li>If the operation was successful, logs at DEBUG level
     *   <li>If the operation failed with an exception, logs at ERROR level with the exception
     *   <li>If the operation was cancelled, logs at WARN level
     * </ul>
     *
     * <p>After logging, if a custom completion consumer was provided during construction, it will
     * be executed with the future as its argument.
     *
     * <p>This method is thread-safe and can be called from any thread.
     *
     * @param future the completed future representing the write operation result
     */
    @Override
    public void operationComplete(T future) {
        if (future.isSuccess()) {
            logger.debug(LoggerUtils.TRANSPORT, "I/O operation was completed successfully.");
        } else if (future.cause() != null) {
            logger.error(
                    LoggerUtils.TRANSPORT,
                    "I/O operation was completed with failure.",
                    future.cause());
        } else if (future.isCancelled()) {
            logger.warn(LoggerUtils.TRANSPORT, "I/O operation was completed by cancellation.");
        }
        if (futureConsumer != null) {
            futureConsumer.accept(future);
        }
    }
}