package org.mockserver.configuration;

import com.google.common.collect.Sets;
import com.google.common.net.InetAddresses;
import org.mockserver.log.model.LogEntry;
import org.mockserver.model.BinaryProxyListener;
import org.mockserver.model.Delay;
import org.mockserver.model.ProxyPassMapping;
import org.mockserver.socket.tls.ForwardProxyTLSX509CertificatesTrustManager;
import org.slf4j.event.Level;

import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.substringBefore;
import static org.mockserver.configuration.ConfigurationProperties.fileExists;

/**
 * @author jamesdbloom
 */
@SuppressWarnings({"UnusedReturnValue", "unused"})
public class Configuration {

    public static Configuration configuration() {
        return new Configuration();
    }

    // logging
    private Level logLevel;
    private Consumer<LogEntry> logEventListener;
    private Boolean disableSystemOut;
    private Boolean disableLogging;
    private Boolean detailedMatchFailures;
    private Boolean launchUIForLogLevelDebug;
    private Boolean metricsEnabled;
    private Long slowRequestThresholdMillis;
    private Boolean metricsRequestDurationRouteLabels;
    private Boolean otelPropagateTraceContext;
    private Boolean otelGenerateTraceId;
    private Boolean mcpEnabled;
    private Boolean wasmEnabled;
    private Integer wasmMaxMemoryPages;
    private String grpcDescriptorDirectory;
    private String grpcProtoDirectory;
    private Boolean grpcEnabled;
    private String grpcProtocPath;
    private Boolean dnsEnabled;
    private Integer dnsPort;
    private Integer http3Port;
    private Map<String, String> logLevelOverrides;
    private Boolean compactLogFormat;

    // memory usage
    private Integer maxExpectations;
    private Integer maxLogEntries;
    private Integer maxWebSocketExpectations;
    private Boolean outputMemoryUsageCsv;
    private String memoryUsageCsvDirectory;

    // scalability
    private Integer nioEventLoopThreadCount;
    private Integer actionHandlerThreadCount;
    private Integer clientNioEventLoopThreadCount;
    private Integer webSocketClientEventLoopThreadCount;
    private Long maxFutureTimeoutInMillis;
    private Boolean matchersFailFast;

    // socket
    private Long maxSocketTimeoutInMillis;
    private Long socketConnectionTimeoutInMillis;
    private Delay connectionDelay;
    private Boolean alwaysCloseSocketConnections;
    private String localBoundIP;

    // http request parsing
    private Integer maxInitialLineLength;
    private Integer maxHeaderSize;
    private Integer maxChunkSize;
    private Integer maxRequestBodySize;
    private Integer maxResponseBodySize;
    private Integer maxLlmConversationBodySize;
    private Boolean driftSemanticAnalysisEnabled;
    private Long driftResponseTimeThresholdMs;
    private Boolean useSemicolonAsQueryParameterSeparator;
    private Boolean assumeAllRequestsAreHttp;
    private Boolean http2Enabled;

    // matcher safety — global only (ConfigurationProperties), no per-instance override:
    // RegexStringMatcher and XPathEvaluator are constructed without a Configuration handle
    // and read directly from ConfigurationProperties, so per-instance setters would be dead API.

    // streaming proxy
    private Boolean streamingResponsesEnabled;
    private Integer maxStreamingCaptureBytes;
    private Integer streamIdleTimeoutSeconds;

    // non http proxying
    private Boolean forwardBinaryRequestsWithoutWaitingForResponse;
    private BinaryProxyListener binaryProxyListener;

    // CORS
    private Boolean enableCORSForAPI;
    private Boolean enableCORSForAllResponses;
    private String corsAllowOrigin;
    private String corsAllowMethods;
    private String corsAllowHeaders;
    private Boolean corsAllowCredentials;
    private Integer corsMaxAgeInSeconds;

    // template restrictions
    private String javascriptDisallowedClasses;
    private String javascriptDisallowedText;
    private Boolean velocityDisallowClassLoading;
    private String velocityDisallowedText;
    private String mustacheDisallowedText;

    // mock initialization
    private String initializationClass;
    private String initializationJsonPath;
    private String initializationOpenAPIPath;
    private String openAPIContextPathPrefix;
    private Boolean openAPIResponseValidation;
    private Boolean watchInitializationJson;

    // mock persistence
    private Boolean persistExpectations;
    private String persistedExpectationsPath;

    // recorded expectation persistence
    private Boolean persistRecordedExpectations;
    private String persistedRecordedExpectationsPath;

    // verification
    private Integer maximumNumberOfRequestToReturnInVerificationFailure;
    private Boolean detailedVerificationFailures;

    // proxy
    // volatile: mutated at runtime via PUT /mockserver/mode (control-plane thread) and read on the
    // Netty request path (HttpActionHandler), so the write must be visible across I/O threads
    private volatile Boolean attemptToProxyIfNoMatchingExpectation;
    private InetSocketAddress forwardHttpProxy;
    private InetSocketAddress forwardHttpsProxy;
    private InetSocketAddress forwardSocksProxy;
    private String forwardProxyAuthenticationUsername;
    private String forwardProxyAuthenticationPassword;
    private String proxyAuthenticationRealm;
    private String proxyAuthenticationUsername;
    private String proxyAuthenticationPassword;
    private String noProxyHosts;
    private String proxyRemoteHost;
    private Integer proxyRemotePort;
    private Boolean forwardAdjustHostHeader;
    private String forwardDefaultHostHeader;
    private List<ProxyPassMapping> proxyPassMappings;

    // global response delay
    private Long globalResponseDelayMillis;

    // liveness
    private String livenessHttpGetPath;

    // control plane authentication
    private Boolean controlPlaneTLSMutualAuthenticationRequired;
    private String controlPlaneTLSMutualAuthenticationCAChain;
    private String controlPlanePrivateKeyPath;
    private String controlPlaneX509CertificatePath;
    private Boolean controlPlaneJWTAuthenticationRequired;
    private String controlPlaneJWTAuthenticationJWKSource;
    private String controlPlaneJWTAuthenticationExpectedAudience;
    private Map<String, String> controlPlaneJWTAuthenticationMatchingClaims;
    private Set<String> controlPlaneJWTAuthenticationRequiredClaims;

    // TLS
    private Boolean proactivelyInitialiseTLS;
    private volatile boolean rebuildTLSContext;
    private volatile boolean rebuildServerTLSContext;
    private String tlsProtocols;
    private Boolean tlsAllowInsecureProtocols;

    // inbound - dynamic CA
    private Boolean dynamicallyCreateCertificateAuthorityCertificate;
    private String directoryToSaveDynamicSSLCertificate;

    // inbound - dynamic private key & x509
    private Boolean preventCertificateDynamicUpdate;
    private String sslCertificateDomainName;
    private Set<String> sslSubjectAlternativeNameDomains;
    private Set<String> sslSubjectAlternativeNameIps;

    // inbound - fixed CA
    private String certificateAuthorityPrivateKey;
    private String certificateAuthorityCertificate;

    // inbound - fixed private key & x509
    private String privateKeyPath;
    private String x509CertificatePath;

    // inbound - mTLS
    private Boolean tlsMutualAuthenticationRequired;
    private String tlsMutualAuthenticationCertificateChain;

    // outbound - CA
    private ForwardProxyTLSX509CertificatesTrustManager forwardProxyTLSX509CertificatesTrustManagerType;

    // outbound - SSRF protection
    private Boolean forwardProxyBlockPrivateNetworks;

    // outbound - fixed CA
    private String forwardProxyTLSCustomTrustX509Certificates;

    // outbound - fixed private key & x509
    private String forwardProxyPrivateKey;
    private String forwardProxyCertificateChain;

    // service mesh / sidecar
    private Boolean transparentProxyEnabled;

    // async messaging defaults
    private String asyncKafkaBootstrapServers;
    private String asyncMqttBrokerUrl;
    private Integer asyncRecordedMessageMaxEntries;


    public Level logLevel() {
        if (logLevel == null) {
            return ConfigurationProperties.logLevel();
        }
        return logLevel;
    }

    /**
     * Override the default logging level of INFO
     *
     * @param level the log level, which can be TRACE, DEBUG, INFO, WARN, ERROR, OFF, FINEST, FINE, INFO, WARNING, SEVERE
     */
    public Configuration logLevel(Level level) {
        this.logLevel = level;
        return this;
    }

    /**
     * Override the default logging level of INFO
     *
     * @param level the log level, which can be TRACE, DEBUG, INFO, WARN, ERROR, OFF, FINEST, FINE, INFO, WARNING, SEVERE
     */
    public Configuration logLevel(String level) {
        this.logLevel = Level.valueOf(level);
        return this;
    }

    public Consumer<LogEntry> logEventListener() {
        return logEventListener;
    }

    public Configuration logEventListener(Consumer<LogEntry> logEventListener) {
        this.logEventListener = logEventListener;
        return this;
    }

    public Boolean disableSystemOut() {
        if (disableSystemOut == null) {
            return ConfigurationProperties.disableSystemOut();
        }
        return disableSystemOut;
    }

    /**
     * Disable printing log to system out for JVM, default is enabled
     *
     * @param disableSystemOut printing log to system out for JVM
     */
    public Configuration disableSystemOut(Boolean disableSystemOut) {
        this.disableSystemOut = disableSystemOut;
        return this;
    }

    public Boolean disableLogging() {
        if (disableLogging == null) {
            return ConfigurationProperties.disableLogging();
        }
        return disableLogging;
    }

    /**
     * Disable all logging and processing of log events
     * <p>
     * The default is false
     *
     * @param disableLogging disable all logging
     */
    public Configuration disableLogging(Boolean disableLogging) {
        this.disableLogging = disableLogging;
        return this;
    }

    public Boolean detailedMatchFailures() {
        if (detailedMatchFailures == null) {
            return ConfigurationProperties.detailedMatchFailures();
        }
        return detailedMatchFailures;
    }

    /**
     * If true (the default) the log event recording that a request matcher did not match will include a detailed reason why each non-matching field did not match.
     *
     * @param detailedMatchFailures enabled detailed match failure log events
     */
    public Configuration detailedMatchFailures(Boolean detailedMatchFailures) {
        this.detailedMatchFailures = detailedMatchFailures;
        return this;
    }

    public Boolean launchUIForLogLevelDebug() {
        if (launchUIForLogLevelDebug == null) {
            return ConfigurationProperties.launchUIForLogLevelDebug();
        }
        return launchUIForLogLevelDebug;
    }

    /**
     * If true (the default) the ClientAndServer constructor will open the UI in the default browser when the log level is set to DEBUG.
     *
     * @param launchUIForLogLevelDebug enabled ClientAndServer constructor launching UI when log level is DEBUG
     */
    public Configuration launchUIForLogLevelDebug(Boolean launchUIForLogLevelDebug) {
        this.launchUIForLogLevelDebug = launchUIForLogLevelDebug;
        return this;
    }

    public Boolean metricsEnabled() {
        if (metricsEnabled == null) {
            return ConfigurationProperties.metricsEnabled();
        }
        return metricsEnabled;
    }

    /**
     * Enable gathering of metrics, default is false
     *
     * @param metricsEnabled enable metrics
     */
    public Configuration metricsEnabled(Boolean metricsEnabled) {
        this.metricsEnabled = metricsEnabled;
        return this;
    }

    public Long slowRequestThresholdMillis() {
        if (slowRequestThresholdMillis == null) {
            return ConfigurationProperties.slowRequestThresholdMillis();
        }
        return slowRequestThresholdMillis;
    }

    /**
     * Threshold in milliseconds for flagging slow forwarded requests. When a forwarded
     * request's total time exceeds this threshold, a WARN-level log entry is emitted and
     * the {@code mock_server_slow_requests_total} Prometheus counter is incremented.
     * <p>
     * Default is 0 (disabled).
     *
     * @param slowRequestThresholdMillis threshold in milliseconds, 0 to disable
     */
    public Configuration slowRequestThresholdMillis(Long slowRequestThresholdMillis) {
        this.slowRequestThresholdMillis = slowRequestThresholdMillis;
        return this;
    }

    public Boolean metricsRequestDurationRouteLabels() {
        if (metricsRequestDurationRouteLabels == null) {
            return ConfigurationProperties.metricsRequestDurationRouteLabels();
        }
        return metricsRequestDurationRouteLabels;
    }

    /**
     * Enable per-route (HTTP method) labels on the request duration histogram.
     *
     * @param metricsRequestDurationRouteLabels enable method labels
     */
    public Configuration metricsRequestDurationRouteLabels(Boolean metricsRequestDurationRouteLabels) {
        this.metricsRequestDurationRouteLabels = metricsRequestDurationRouteLabels;
        return this;
    }

    public Boolean otelPropagateTraceContext() {
        if (otelPropagateTraceContext == null) {
            return ConfigurationProperties.otelPropagateTraceContext();
        }
        return otelPropagateTraceContext;
    }

    /**
     * When true, MockServer copies the incoming W3C traceparent and tracestate
     * headers into mock responses. Off by default.
     *
     * @param otelPropagateTraceContext enable trace context propagation to responses
     */
    public Configuration otelPropagateTraceContext(Boolean otelPropagateTraceContext) {
        this.otelPropagateTraceContext = otelPropagateTraceContext;
        return this;
    }

    public Boolean otelGenerateTraceId() {
        if (otelGenerateTraceId == null) {
            return ConfigurationProperties.otelGenerateTraceId();
        }
        return otelGenerateTraceId;
    }

    /**
     * When true, MockServer generates a new W3C trace ID for incoming requests
     * that do not carry a traceparent header. Off by default.
     *
     * @param otelGenerateTraceId enable trace ID generation for requests without traceparent
     */
    public Configuration otelGenerateTraceId(Boolean otelGenerateTraceId) {
        this.otelGenerateTraceId = otelGenerateTraceId;
        return this;
    }

    public Boolean mcpEnabled() {
        if (mcpEnabled == null) {
            return ConfigurationProperties.mcpEnabled();
        }
        return mcpEnabled;
    }

    public Configuration mcpEnabled(Boolean mcpEnabled) {
        this.mcpEnabled = mcpEnabled;
        return this;
    }

    public Boolean wasmEnabled() {
        if (wasmEnabled == null) {
            return ConfigurationProperties.wasmEnabled();
        }
        return wasmEnabled;
    }

    public Configuration wasmEnabled(Boolean wasmEnabled) {
        this.wasmEnabled = wasmEnabled;
        return this;
    }

    public Integer wasmMaxMemoryPages() {
        if (wasmMaxMemoryPages == null) {
            return ConfigurationProperties.wasmMaxMemoryPages();
        }
        return wasmMaxMemoryPages;
    }

    public Configuration wasmMaxMemoryPages(Integer wasmMaxMemoryPages) {
        this.wasmMaxMemoryPages = wasmMaxMemoryPages;
        return this;
    }

    public String grpcDescriptorDirectory() {
        if (grpcDescriptorDirectory == null) {
            return ConfigurationProperties.grpcDescriptorDirectory();
        }
        return grpcDescriptorDirectory;
    }

    public Configuration grpcDescriptorDirectory(String grpcDescriptorDirectory) {
        this.grpcDescriptorDirectory = grpcDescriptorDirectory;
        return this;
    }

    public String grpcProtoDirectory() {
        if (grpcProtoDirectory == null) {
            return ConfigurationProperties.grpcProtoDirectory();
        }
        return grpcProtoDirectory;
    }

    public Configuration grpcProtoDirectory(String grpcProtoDirectory) {
        this.grpcProtoDirectory = grpcProtoDirectory;
        return this;
    }

    public Boolean grpcEnabled() {
        if (grpcEnabled == null) {
            return ConfigurationProperties.grpcEnabled();
        }
        return grpcEnabled;
    }

    public Configuration grpcEnabled(Boolean grpcEnabled) {
        this.grpcEnabled = grpcEnabled;
        return this;
    }

    public String grpcProtocPath() {
        if (grpcProtocPath == null) {
            return ConfigurationProperties.grpcProtocPath();
        }
        return grpcProtocPath;
    }

    public Configuration grpcProtocPath(String grpcProtocPath) {
        this.grpcProtocPath = grpcProtocPath;
        return this;
    }

    public Boolean dnsEnabled() {
        if (dnsEnabled == null) {
            return ConfigurationProperties.dnsEnabled();
        }
        return dnsEnabled;
    }

    public Configuration dnsEnabled(Boolean dnsEnabled) {
        this.dnsEnabled = dnsEnabled;
        return this;
    }

    public Integer dnsPort() {
        if (dnsPort == null) {
            return ConfigurationProperties.dnsPort();
        }
        return dnsPort;
    }

    public Configuration dnsPort(Integer dnsPort) {
        this.dnsPort = dnsPort;
        return this;
    }

    public Integer http3Port() {
        if (http3Port == null) {
            return ConfigurationProperties.http3Port();
        }
        return http3Port;
    }

    public Configuration http3Port(Integer http3Port) {
        this.http3Port = http3Port;
        return this;
    }

    public Map<String, String> logLevelOverrides() {
        if (logLevelOverrides == null) {
            return ConfigurationProperties.logLevelOverrides();
        }
        return logLevelOverrides;
    }

    public Configuration logLevelOverrides(Map<String, String> logLevelOverrides) {
        this.logLevelOverrides = logLevelOverrides;
        return this;
    }

    public Boolean compactLogFormat() {
        if (compactLogFormat == null) {
            return ConfigurationProperties.compactLogFormat();
        }
        return compactLogFormat;
    }

    public Configuration compactLogFormat(Boolean compactLogFormat) {
        this.compactLogFormat = compactLogFormat;
        return this;
    }

    public Integer maxExpectations() {
        if (maxExpectations == null) {
            return ConfigurationProperties.maxExpectations();
        }
        return maxExpectations;
    }

    /**
     * <p>
     * Maximum number of expectations stored in memory.  Expectations are stored in a circular queue so once this limit is reach the oldest and lowest priority expectations are overwritten
     * </p>
     * <p>
     * The default maximum depends on the available memory in the JVM with an upper limit of 15000
     * </p>
     *
     * @param maxExpectations maximum number of expectations to store
     */
    public Configuration maxExpectations(Integer maxExpectations) {
        this.maxExpectations = maxExpectations;
        return this;
    }

    public Integer maxLogEntries() {
        if (maxLogEntries == null) {
            return ConfigurationProperties.maxLogEntries();
        }
        return maxLogEntries;
    }

    /**
     * <p>
     * Maximum number of log entries stored in memory.  Log entries are stored in a circular queue so once this limit is reach the oldest log entries are overwritten
     * </p>
     * <p>
     * The default maximum depends on the available memory in the JVM with an upper limit of 100000
     * </p>
     *
     * @param maxLogEntries maximum number of expectations to store
     */
    public Configuration maxLogEntries(Integer maxLogEntries) {
        this.maxLogEntries = maxLogEntries;
        return this;
    }

    public Integer maxWebSocketExpectations() {
        if (maxWebSocketExpectations == null) {
            return ConfigurationProperties.maxWebSocketExpectations();
        }
        return maxWebSocketExpectations;
    }

    /**
     * <p>
     * Maximum number of remote (not the same JVM) method callbacks (i.e. web sockets) registered for expectations.  The web socket client registry entries are stored in a circular queue so once this limit is reach the oldest are overwritten.
     * </p>
     * <p>
     * The default is 1500
     * </p>
     *
     * @param maxWebSocketExpectations maximum number of method callbacks (i.e. web sockets) registered for expectations
     */
    public Configuration maxWebSocketExpectations(Integer maxWebSocketExpectations) {
        this.maxWebSocketExpectations = maxWebSocketExpectations;
        return this;
    }

    public Boolean outputMemoryUsageCsv() {
        if (outputMemoryUsageCsv == null) {
            return ConfigurationProperties.outputMemoryUsageCsv();
        }
        return outputMemoryUsageCsv;
    }

    /**
     * <p>Output JVM memory usage metrics to CSV file periodically called <strong>memoryUsage_&lt;yyyy-MM-dd&gt;.csv</strong></p>
     *
     * @param outputMemoryUsageCsv output of JVM memory metrics
     */
    public Configuration outputMemoryUsageCsv(Boolean outputMemoryUsageCsv) {
        this.outputMemoryUsageCsv = outputMemoryUsageCsv;
        return this;
    }

    public String memoryUsageCsvDirectory() {
        if (memoryUsageCsvDirectory == null) {
            return ConfigurationProperties.memoryUsageCsvDirectory();
        }
        return memoryUsageCsvDirectory;
    }

    /**
     * <p>Directory to output JVM memory usage metrics CSV files to when outputMemoryUsageCsv enabled</p>
     *
     * @param memoryUsageCsvDirectory directory to save JVM memory metrics CSV files
     */
    public Configuration memoryUsageCsvDirectory(String memoryUsageCsvDirectory) {
        this.memoryUsageCsvDirectory = memoryUsageCsvDirectory;
        return this;
    }

    public Integer nioEventLoopThreadCount() {
        if (nioEventLoopThreadCount == null) {
            return ConfigurationProperties.nioEventLoopThreadCount();
        }
        return nioEventLoopThreadCount;
    }

    /**
     * <p>Netty worker thread pool size for handling requests and response.  These threads handle deserializing and serialising HTTP requests and responses and some other fast logic, long running tasks are done on the action handler thread pool.</p>
     *
     * @param nioEventLoopThreadCount Netty worker thread pool size
     */
    public Configuration nioEventLoopThreadCount(Integer nioEventLoopThreadCount) {
        this.nioEventLoopThreadCount = nioEventLoopThreadCount;
        return this;
    }

    public Integer actionHandlerThreadCount() {
        if (actionHandlerThreadCount == null) {
            return ConfigurationProperties.actionHandlerThreadCount();
        }
        return actionHandlerThreadCount;
    }

    /**
     * <p>Number of threads for the action handler thread pool</p>
     * <p>These threads are used for handling actions such as:</p>
     *     <ul>
     *         <li>serialising and writing expectation or proxied responses</li>
     *         <li>handling response delays in a non-blocking way (i.e. using a scheduler)</li>
     *         <li>executing class callbacks</li>
     *         <li>handling method / closure callbacks (using web sockets)</li>
     *     </ul>
     * <p>
     * <p>Default is maximum of 5 or available processors count</p>
     *
     * @param actionHandlerThreadCount Netty worker thread pool size
     */
    public Configuration actionHandlerThreadCount(Integer actionHandlerThreadCount) {
        this.actionHandlerThreadCount = actionHandlerThreadCount;
        return this;
    }

    public Integer clientNioEventLoopThreadCount() {
        if (clientNioEventLoopThreadCount == null) {
            return ConfigurationProperties.clientNioEventLoopThreadCount();
        }
        return clientNioEventLoopThreadCount;
    }

    /**
     * <p>Client Netty worker thread pool size for handling requests and response.  These threads handle deserializing and serialising HTTP requests and responses and some other fast logic.</p>
     *
     * <p>Default is 5 threads</p>
     *
     * @param clientNioEventLoopThreadCount Client Netty worker thread pool size
     */
    public Configuration clientNioEventLoopThreadCount(Integer clientNioEventLoopThreadCount) {
        this.clientNioEventLoopThreadCount = clientNioEventLoopThreadCount;
        return this;
    }

    public Integer webSocketClientEventLoopThreadCount() {
        if (webSocketClientEventLoopThreadCount == null) {
            return ConfigurationProperties.webSocketClientEventLoopThreadCount();
        }
        return webSocketClientEventLoopThreadCount;
    }

    /**
     * <p>Client Netty worker thread pool size for handling requests and response.  These threads handle deserializing and serialising HTTP requests and responses and some other fast logic.</p>
     *
     * <p>Default is 5 threads</p>
     *
     * @param webSocketClientEventLoopThreadCount Client Netty worker thread pool size
     */
    public Configuration webSocketClientEventLoopThreadCount(Integer webSocketClientEventLoopThreadCount) {
        this.webSocketClientEventLoopThreadCount = webSocketClientEventLoopThreadCount;
        return this;
    }

    public Long maxFutureTimeoutInMillis() {
        if (maxFutureTimeoutInMillis == null) {
            return ConfigurationProperties.maxFutureTimeout();
        }
        return maxFutureTimeoutInMillis;
    }

    /**
     * Maximum time allowed in milliseconds for any future to wait, for example when waiting for a response over a web socket callback.
     * <p>
     * Default is 60,000 ms
     *
     * @param maxFutureTimeoutInMillis maximum time allowed in milliseconds
     */
    public Configuration maxFutureTimeoutInMillis(Long maxFutureTimeoutInMillis) {
        this.maxFutureTimeoutInMillis = maxFutureTimeoutInMillis;
        return this;
    }

    public Boolean matchersFailFast() {
        if (matchersFailFast == null) {
            return ConfigurationProperties.matchersFailFast();
        }
        return matchersFailFast;
    }

    /**
     * If true (the default) request matchers will fail on the first non-matching field, if false request matchers will compare all fields.
     * This is useful to see all mismatching fields in the log event recording that a request matcher did not match.
     *
     * @param matchersFailFast enabled request matchers failing fast
     */
    public Configuration matchersFailFast(Boolean matchersFailFast) {
        this.matchersFailFast = matchersFailFast;
        return this;
    }

    public Long maxSocketTimeoutInMillis() {
        if (maxSocketTimeoutInMillis == null) {
            return ConfigurationProperties.maxSocketTimeout();
        }
        return maxSocketTimeoutInMillis;
    }

    /**
     * Maximum time in milliseconds allowed for a response from a socket
     * <p>
     * Default is 20,000 ms
     *
     * @param maxSocketTimeoutInMillis maximum time in milliseconds allowed
     */
    public Configuration maxSocketTimeoutInMillis(Long maxSocketTimeoutInMillis) {
        this.maxSocketTimeoutInMillis = maxSocketTimeoutInMillis;
        return this;
    }

    public Long socketConnectionTimeoutInMillis() {
        if (socketConnectionTimeoutInMillis == null) {
            return ConfigurationProperties.socketConnectionTimeout();
        }
        return socketConnectionTimeoutInMillis;
    }

    /**
     * Maximum time in milliseconds allowed to connect to a socket
     * <p>
     * Default is 20,000 ms
     *
     * @param socketConnectionTimeoutInMillis maximum time allowed in milliseconds
     */
    public Configuration socketConnectionTimeoutInMillis(Long socketConnectionTimeoutInMillis) {
        this.socketConnectionTimeoutInMillis = socketConnectionTimeoutInMillis;
        return this;
    }

    public Delay connectionDelay() {
        return connectionDelay;
    }

    public Configuration connectionDelay(Delay connectionDelay) {
        this.connectionDelay = connectionDelay;
        return this;
    }

    public Boolean alwaysCloseSocketConnections() {
        if (alwaysCloseSocketConnections == null) {
            return ConfigurationProperties.alwaysCloseSocketConnections();
        }
        return alwaysCloseSocketConnections;
    }

    /**
     * <p>If true socket connections will always be closed after a response is returned, if false connection is only closed if request header indicate connection should be closed.</p>
     * <p>
     * Default is false
     *
     * @param alwaysCloseSocketConnections true socket connections will always be closed after a response is returned
     */
    public Configuration alwaysCloseSocketConnections(Boolean alwaysCloseSocketConnections) {
        this.alwaysCloseSocketConnections = alwaysCloseSocketConnections;
        return this;
    }

    public String localBoundIP() {
        if (localBoundIP == null) {
            return ConfigurationProperties.localBoundIP();
        }
        return localBoundIP;
    }

    /**
     * The local IP address to bind to for accepting new socket connections
     * <p>
     * Default is 0.0.0.0
     *
     * @param localBoundIP local IP address to bind to for accepting new socket connections
     */
    public Configuration localBoundIP(String localBoundIP) {
        this.localBoundIP = localBoundIP;
        return this;
    }

    public Integer maxInitialLineLength() {
        if (maxInitialLineLength == null) {
            return ConfigurationProperties.maxInitialLineLength();
        }
        return maxInitialLineLength;
    }

    /**
     * Maximum size of the first line of an HTTP request
     * <p>
     * The default is Integer.MAX_VALUE
     *
     * @param maxInitialLineLength maximum size of the first line of an HTTP request
     */
    public Configuration maxInitialLineLength(Integer maxInitialLineLength) {
        this.maxInitialLineLength = maxInitialLineLength;
        return this;
    }

    public Integer maxHeaderSize() {
        if (maxHeaderSize == null) {
            return ConfigurationProperties.maxHeaderSize();
        }
        return maxHeaderSize;
    }

    /**
     * Maximum size of HTTP request headers
     * <p>
     * The default is Integer.MAX_VALUE
     *
     * @param maxHeaderSize maximum size of HTTP request headers
     */
    public Configuration maxHeaderSize(Integer maxHeaderSize) {
        this.maxHeaderSize = maxHeaderSize;
        return this;
    }

    public Integer maxChunkSize() {
        if (maxChunkSize == null) {
            return ConfigurationProperties.maxChunkSize();
        }
        return maxChunkSize;
    }

    /**
     * Maximum size of HTTP chunks in request or responses
     * <p>
     * The default is Integer.MAX_VALUE
     *
     * @param maxChunkSize maximum size of HTTP chunks in request or responses
     */
    public Configuration maxChunkSize(Integer maxChunkSize) {
        this.maxChunkSize = maxChunkSize;
        return this;
    }

    public Integer maxRequestBodySize() {
        if (maxRequestBodySize == null) {
            return ConfigurationProperties.maxRequestBodySize();
        }
        return maxRequestBodySize;
    }

    /**
     * Maximum aggregated body size (in bytes) accepted on inbound HTTP/1.1 and HTTP/2 requests.
     * <p>
     * The default is 10,485,760 bytes (10 MiB).
     *
     * @param maxRequestBodySize maximum inbound request body size in bytes
     */
    public Configuration maxRequestBodySize(Integer maxRequestBodySize) {
        this.maxRequestBodySize = maxRequestBodySize;
        return this;
    }

    public Integer maxResponseBodySize() {
        if (maxResponseBodySize == null) {
            return ConfigurationProperties.maxResponseBodySize();
        }
        return maxResponseBodySize;
    }

    /**
     * Maximum aggregated body size (in bytes) accepted on responses received from upstream
     * servers when MockServer is acting as a proxy or forwarder.
     * <p>
     * The default is 52,428,800 bytes (50 MiB).
     *
     * @param maxResponseBodySize maximum upstream response body size in bytes
     */
    public Configuration maxResponseBodySize(Integer maxResponseBodySize) {
        this.maxResponseBodySize = maxResponseBodySize;
        return this;
    }

    public Integer maxLlmConversationBodySize() {
        if (maxLlmConversationBodySize == null) {
            return ConfigurationProperties.maxLlmConversationBodySize();
        }
        return maxLlmConversationBodySize;
    }

    /**
     * Maximum body size (in bytes) for LLM conversation request bodies.
     * <p>
     * The default is 1,048,576 bytes (1 MiB). Valid range is [16384, 67108864].
     *
     * @param maxLlmConversationBodySize maximum LLM conversation body size in bytes
     */
    public Configuration maxLlmConversationBodySize(Integer maxLlmConversationBodySize) {
        this.maxLlmConversationBodySize = maxLlmConversationBodySize;
        return this;
    }

    public Boolean driftSemanticAnalysisEnabled() {
        if (driftSemanticAnalysisEnabled == null) {
            return ConfigurationProperties.driftSemanticAnalysisEnabled();
        }
        return driftSemanticAnalysisEnabled;
    }

    /**
     * Whether to enable LLM-powered semantic drift analysis. When enabled and
     * a runtime LLM backend is available, each structural drift record is enriched
     * with a severity classification (BREAKING/WARNING/INFORMATIONAL) and an
     * explanation. Default false (opt-in).
     *
     * @param driftSemanticAnalysisEnabled true to enable semantic drift analysis
     */
    public Configuration driftSemanticAnalysisEnabled(Boolean driftSemanticAnalysisEnabled) {
        this.driftSemanticAnalysisEnabled = driftSemanticAnalysisEnabled;
        return this;
    }

    public Long driftResponseTimeThresholdMs() {
        if (driftResponseTimeThresholdMs == null) {
            return ConfigurationProperties.driftResponseTimeThresholdMs();
        }
        return driftResponseTimeThresholdMs;
    }

    /**
     * p95 response time threshold (in milliseconds) for performance drift detection.
     * When positive, a PERFORMANCE drift record is emitted whenever the p95 response
     * time for an expectation exceeds this threshold. Default 0 (disabled).
     *
     * @param driftResponseTimeThresholdMs threshold in milliseconds, 0 to disable
     */
    public Configuration driftResponseTimeThresholdMs(Long driftResponseTimeThresholdMs) {
        this.driftResponseTimeThresholdMs = driftResponseTimeThresholdMs;
        return this;
    }

    // regexMatchingTimeoutMillis / xpathMatchingTimeoutMillis are intentionally NOT exposed as
    // per-instance Configuration getters/setters: the matchers that consume them are constructed
    // without a Configuration handle and read directly from ConfigurationProperties. Use
    // ConfigurationProperties.regexMatchingTimeoutMillis(...) / xpathMatchingTimeoutMillis(...) to
    // override the JVM-wide default.

    public Boolean useSemicolonAsQueryParameterSeparator() {
        if (useSemicolonAsQueryParameterSeparator == null) {
            return ConfigurationProperties.useSemicolonAsQueryParameterSeparator();
        }
        return useSemicolonAsQueryParameterSeparator;
    }

    /**
     * If true semicolons are treated as a separator for a query parameter string, if false the semicolon is treated as a normal character that is part of a query parameter value.
     * <p>
     * The default is true
     *
     * @param useSemicolonAsQueryParameterSeparator if true semicolons are treated as a separator for a query parameter string
     */
    public Configuration useSemicolonAsQueryParameterSeparator(Boolean useSemicolonAsQueryParameterSeparator) {
        this.useSemicolonAsQueryParameterSeparator = useSemicolonAsQueryParameterSeparator;
        return this;
    }

    public Boolean assumeAllRequestsAreHttp() {
        if (assumeAllRequestsAreHttp == null) {
            return ConfigurationProperties.assumeAllRequestsAreHttp();
        }
        return assumeAllRequestsAreHttp;
    }

    /**
     * If false requests are assumed as binary if the method isn't one of "GET", "POST", "PUT", "HEAD", "OPTIONS", "PATCH", "DELETE", "TRACE" or "CONNECT"
     * <p>
     * The default is false
     *
     * @param assumeAllRequestsAreHttp if false requests are assumed as binary if the method isn't one of "GET", "POST", "PUT", "HEAD", "OPTIONS", "PATCH", "DELETE", "TRACE" or "CONNECT"
     */
    public Configuration assumeAllRequestsAreHttp(Boolean assumeAllRequestsAreHttp) {
        this.assumeAllRequestsAreHttp = assumeAllRequestsAreHttp;
        return this;
    }

    public Boolean http2Enabled() {
        if (http2Enabled == null) {
            return ConfigurationProperties.http2Enabled();
        }
        return http2Enabled;
    }

    /**
     * If false HTTP/2 is disabled and ALPN no longer advertises h2, so HTTP/2 capable clients are
     * forced to use HTTP/1.1 (and the HTTP/2 cleartext h2c upgrade is not detected)
     * <p>
     * The default is true
     *
     * @param http2Enabled if false HTTP/2 is disabled and clients are forced to use HTTP/1.1
     */
    public Configuration http2Enabled(Boolean http2Enabled) {
        this.http2Enabled = http2Enabled;
        return this;
    }

    public Boolean streamingResponsesEnabled() {
        if (streamingResponsesEnabled == null) {
            return ConfigurationProperties.streamingResponsesEnabled();
        }
        return streamingResponsesEnabled;
    }

    /**
     * If true (the default) streaming responses (Server-Sent Events with {@code Content-Type: text/event-stream})
     * received while proxying are relayed to the client incrementally as they arrive, instead of being fully
     * buffered before being forwarded. This keeps streaming APIs (such as LLM APIs) responsive when proxied.
     * Only SSE responses are detected as streaming; ordinary chunked responses are aggregated normally.
     * <p>
     * Default is true
     *
     * @param streamingResponsesEnabled enable incremental relay of streaming responses while proxying
     */
    public Configuration streamingResponsesEnabled(Boolean streamingResponsesEnabled) {
        this.streamingResponsesEnabled = streamingResponsesEnabled;
        return this;
    }

    public Integer maxStreamingCaptureBytes() {
        if (maxStreamingCaptureBytes == null) {
            return ConfigurationProperties.maxStreamingCaptureBytes();
        }
        return Math.max(0, maxStreamingCaptureBytes);
    }

    /**
     * The maximum number of bytes of a streaming response body captured into the event log while relaying it.
     * The full stream is always relayed to the client; this only bounds how much is retained for the dashboard
     * and retrieve API. Once exceeded the logged body is truncated and flagged.
     * <p>
     * Default is 262144 (256 KB)
     *
     * @param maxStreamingCaptureBytes maximum number of streaming response body bytes captured into the event log
     */
    public Configuration maxStreamingCaptureBytes(Integer maxStreamingCaptureBytes) {
        this.maxStreamingCaptureBytes = maxStreamingCaptureBytes;
        return this;
    }

    public Integer streamIdleTimeoutSeconds() {
        if (streamIdleTimeoutSeconds == null) {
            return ConfigurationProperties.streamIdleTimeoutSeconds();
        }
        return Math.max(0, streamIdleTimeoutSeconds);
    }

    /**
     * The maximum time in seconds a streaming response connection may be idle (no chunk received) before it is
     * considered dead and closed. This replaces the fixed socket timeout for streaming responses, which would
     * otherwise terminate long-lived streams.
     * <p>
     * Default is 60 seconds
     *
     * @param streamIdleTimeoutSeconds maximum idle time in seconds between streaming response chunks
     */
    public Configuration streamIdleTimeoutSeconds(Integer streamIdleTimeoutSeconds) {
        this.streamIdleTimeoutSeconds = streamIdleTimeoutSeconds;
        return this;
    }

    public Boolean forwardBinaryRequestsWithoutWaitingForResponse() {
        if (forwardBinaryRequestsWithoutWaitingForResponse == null) {
            return ConfigurationProperties.forwardBinaryRequestsWithoutWaitingForResponse();
        }
        return forwardBinaryRequestsWithoutWaitingForResponse;
    }

    /**
     * If true the BinaryProxyListener is called before a response is received from the
     * remote host. This enables the proxying of messages without a response.
     * <p>
     * The default is false
     *
     * @param forwardBinaryRequestsWithoutWaitingForResponse target value
     */
    public Configuration forwardBinaryRequestsWithoutWaitingForResponse(Boolean forwardBinaryRequestsWithoutWaitingForResponse) {
        this.forwardBinaryRequestsWithoutWaitingForResponse = forwardBinaryRequestsWithoutWaitingForResponse;
        return this;
    }

    public BinaryProxyListener binaryProxyListener() {
        return binaryProxyListener;
    }

    /**
     * Set a org.mockserver.model.BinaryProxyListener called when binary content is proxied
     *
     * @param binaryProxyListener a BinaryProxyListener called when binary content is proxied
     */
    public Configuration binaryProxyListener(BinaryProxyListener binaryProxyListener) {
        this.binaryProxyListener = binaryProxyListener;
        return this;
    }

    public Boolean enableCORSForAPI() {
        if (enableCORSForAPI == null) {
            return ConfigurationProperties.enableCORSForAPI();
        }
        return enableCORSForAPI;
    }

    /**
     * Enable CORS for MockServer REST API so that the API can be used for javascript running in browsers, such as selenium
     * <p>
     * The default is false
     *
     * @param enableCORSForAPI CORS for MockServer REST API
     */
    public Configuration enableCORSForAPI(Boolean enableCORSForAPI) {
        this.enableCORSForAPI = enableCORSForAPI;
        return this;
    }

    public Boolean enableCORSForAllResponses() {
        if (enableCORSForAllResponses == null) {
            return ConfigurationProperties.enableCORSForAllResponses();
        }
        return enableCORSForAllResponses;
    }

    /**
     * Enable CORS for all responses from MockServer, including the REST API and expectation responses
     * <p>
     * The default is false
     *
     * @param enableCORSForAllResponses CORS for all responses from MockServer
     */
    public Configuration enableCORSForAllResponses(Boolean enableCORSForAllResponses) {
        this.enableCORSForAllResponses = enableCORSForAllResponses;
        return this;
    }

    public String corsAllowOrigin() {
        if (corsAllowOrigin == null) {
            return ConfigurationProperties.corsAllowOrigin();
        }
        return corsAllowOrigin;
    }

    /**
     * <p>the value used for CORS in the access-control-allow-origin header.</p>
     * <p>The default is ""</p>
     *
     * @param corsAllowOrigin the value used for CORS in the access-control-allow-methods header
     */
    public Configuration corsAllowOrigin(String corsAllowOrigin) {
        this.corsAllowOrigin = corsAllowOrigin;
        return this;
    }

    public String corsAllowMethods() {
        if (corsAllowMethods == null) {
            return ConfigurationProperties.corsAllowMethods();
        }
        return corsAllowMethods;
    }

    /**
     * <p>the value used for CORS in the access-control-allow-methods header.</p>
     * <p>The default is ""</p>
     *
     * @param corsAllowMethods the value used for CORS in the access-control-allow-methods header
     */
    public Configuration corsAllowMethods(String corsAllowMethods) {
        this.corsAllowMethods = corsAllowMethods;
        return this;
    }

    public String corsAllowHeaders() {
        if (corsAllowHeaders == null) {
            return ConfigurationProperties.corsAllowHeaders();
        }
        return corsAllowHeaders;
    }

    /**
     * <p>the value used for CORS in the access-control-allow-headers and access-control-expose-headers headers.</p>
     * <p>In addition to this default value any headers specified in the request header access-control-request-headers also get added to access-control-allow-headers and access-control-expose-headers headers in a CORS response.</p>
     * <p>The default is ""</p>
     *
     * @param corsAllowHeaders the value used for CORS in the access-control-allow-headers and access-control-expose-headers headers
     */
    public Configuration corsAllowHeaders(String corsAllowHeaders) {
        this.corsAllowHeaders = corsAllowHeaders;
        return this;
    }

    public Boolean corsAllowCredentials() {
        if (corsAllowCredentials == null) {
            return ConfigurationProperties.corsAllowCredentials();
        }
        return corsAllowCredentials;
    }

    /**
     * The value used for CORS in the access-control-allow-credentials header.
     * <p>
     * The default is false
     *
     * @param corsAllowCredentials the value used for CORS in the access-control-allow-credentials header
     */
    public Configuration corsAllowCredentials(Boolean corsAllowCredentials) {
        this.corsAllowCredentials = corsAllowCredentials;
        return this;
    }

    public Integer corsMaxAgeInSeconds() {
        if (corsMaxAgeInSeconds == null) {
            return ConfigurationProperties.corsMaxAgeInSeconds();
        }
        return corsMaxAgeInSeconds;
    }

    /**
     * The value used for CORS in the access-control-max-age header.
     * <p>
     * The default is 0
     *
     * @param corsMaxAgeInSeconds the value used for CORS in the access-control-max-age header.
     */
    public Configuration corsMaxAgeInSeconds(Integer corsMaxAgeInSeconds) {
        this.corsMaxAgeInSeconds = corsMaxAgeInSeconds;
        return this;
    }

    // template restrictions

    public String javascriptDisallowedClasses() {
        if (javascriptDisallowedClasses == null) {
            return ConfigurationProperties.javascriptDisallowedClasses();
        }
        return javascriptDisallowedClasses;
    }

    /**
     * Set comma separate list of classes not allowed to be used by javascript templates
     * <p>
     * The default is all allowed
     *
     * @param javascriptDisallowedClasses comma separated list of classes not allowed to be used
     */
    public Configuration javascriptDisallowedClasses(String javascriptDisallowedClasses) {
        this.javascriptDisallowedClasses = javascriptDisallowedClasses;
        return this;
    }

    public String javascriptDisallowedText() {
        if (javascriptDisallowedText == null) {
            return ConfigurationProperties.javascriptDisallowedText();
        }
        return javascriptDisallowedText;
    }

    /**
     * Set comma separate list of text not allowed to be contained in javascript templates
     * <p>
     * The default is all allowed
     *
     * @param javascriptDisallowedText comma separated list of text not allowed to be contained in javascript templates
     */
    public Configuration javascriptDisallowedText(String javascriptDisallowedText) {
        this.javascriptDisallowedText = javascriptDisallowedText;
        return this;
    }

    public Boolean velocityDisallowClassLoading() {
        if (velocityDisallowClassLoading == null) {
            return ConfigurationProperties.velocityDisallowClassLoading();
        }
        return velocityDisallowClassLoading;
    }

    /**
     * If true class loading is not allowed in velocity templates
     * <p>
     * The default is false
     *
     * @param velocityDisallowClassLoading class loading is not allowed in velocity templates
     */
    public Configuration velocityDisallowClassLoading(Boolean velocityDisallowClassLoading) {
        this.velocityDisallowClassLoading = velocityDisallowClassLoading;
        return this;
    }

    public String velocityDisallowedText() {
        if (velocityDisallowedText == null) {
            return ConfigurationProperties.velocityDisallowedText();
        }
        return velocityDisallowedText;
    }

    /**
     * Set comma separate list of text not allowed to be contained in velocity templates
     * <p>
     * The default is all allowed
     *
     * @param velocityDisallowedText comma separated list of text not allowed to be contained in velocity templates
     */
    public Configuration velocityDisallowedText(String velocityDisallowedText) {
        this.velocityDisallowedText = velocityDisallowedText;
        return this;
    }

    public String mustacheDisallowedText() {
        if (mustacheDisallowedText == null) {
            return ConfigurationProperties.mustacheDisallowedText();
        }
        return mustacheDisallowedText;
    }

    /**
     * Set comma separate list of text not allowed to be contained in mustache templates
     * <p>
     * The default is all allowed
     *
     * @param mustacheDisallowedText comma separated list of text not allowed to be contained in mustache templates
     */
    public Configuration mustacheDisallowedText(String mustacheDisallowedText) {
        this.mustacheDisallowedText = mustacheDisallowedText;
        return this;
    }

    public String initializationClass() {
        if (initializationClass == null) {
            return ConfigurationProperties.initializationClass();
        }
        return initializationClass;
    }

    /**
     * The class (and package) used to initialize expectations in MockServer at startup, if set MockServer will load and call this class to initialize expectations when is starts.
     * <p>
     * The default is null
     *
     * @param initializationClass class (and package) used to initialize expectations in MockServer at startup
     */
    public Configuration initializationClass(String initializationClass) {
        this.initializationClass = initializationClass;
        return this;
    }

    public String initializationJsonPath() {
        if (initializationJsonPath == null) {
            return ConfigurationProperties.initializationJsonPath();
        }
        return initializationJsonPath;
    }

    /**
     * <p>The path to the json file used to initialize expectations in MockServer at startup, if set MockServer will load this file and initialise expectations for each item in the file when is starts.</p>
     * <p>The expected format of the file is a JSON array of expectations, as per the <a target="_blank" href="https://app.swaggerhub.com/apis/jamesdbloom/mock-server-openapi/5.15.x#/Expectations" target="_blank">REST API format</a></p>
     * <p>To watch multiple files use a file globs as documented here: https://mock-server.com/mock_server/initializing_expectations.html#expectation_initializer_json_glob_patterns</p>
     *
     * @param initializationJsonPath path to the json file used to initialize expectations in MockServer at startup
     */
    public Configuration initializationJsonPath(String initializationJsonPath) {
        this.initializationJsonPath = initializationJsonPath;
        return this;
    }

    public String initializationOpenAPIPath() {
        if (initializationOpenAPIPath == null) {
            return ConfigurationProperties.initializationOpenAPIPath();
        }
        return initializationOpenAPIPath;
    }

    /**
     * <p>The path to the OpenAPI spec file used to initialize expectations in MockServer at startup, if set MockServer will load this file and create expectations for each operation when it starts.</p>
     * <p>The file can be a YAML (.yaml, .yml) or JSON (.json) OpenAPI v3 specification.</p>
     * <p>To watch multiple files use file globs as documented here: https://mock-server.com/mock_server/initializing_expectations.html#expectation_initializer_json_glob_patterns</p>
     *
     * @param initializationOpenAPIPath path to the OpenAPI spec file used to initialize expectations in MockServer at startup
     */
    public Configuration initializationOpenAPIPath(String initializationOpenAPIPath) {
        this.initializationOpenAPIPath = initializationOpenAPIPath;
        return this;
    }

    public String openAPIContextPathPrefix() {
        if (openAPIContextPathPrefix == null) {
            return ConfigurationProperties.openAPIContextPathPrefix();
        }
        return openAPIContextPathPrefix;
    }

    /**
     * <p>A path prefix to add to all paths generated from OpenAPI specifications.</p>
     * <p>For example, if set to "/api/v1" then a path "/pets" from the spec becomes "/api/v1/pets".</p>
     *
     * @param openAPIContextPathPrefix the path prefix to add to OpenAPI paths
     */
    public Configuration openAPIContextPathPrefix(String openAPIContextPathPrefix) {
        this.openAPIContextPathPrefix = openAPIContextPathPrefix;
        return this;
    }

    public Boolean openAPIResponseValidation() {
        if (openAPIResponseValidation == null) {
            return ConfigurationProperties.openAPIResponseValidation();
        }
        return openAPIResponseValidation;
    }

    /**
     * <p>If enabled MockServer will validate that mock responses conform to the OpenAPI spec schema they were generated from.</p>
     * <p>Validation is advisory only - responses are still returned to the client even if validation fails.</p>
     *
     * <p>The default is false</p>
     *
     * @param openAPIResponseValidation if enabled mock responses will be validated against the OpenAPI spec schema
     */
    public Configuration openAPIResponseValidation(Boolean openAPIResponseValidation) {
        this.openAPIResponseValidation = openAPIResponseValidation;
        return this;
    }

    public Boolean watchInitializationJson() {
        if (watchInitializationJson == null) {
            return ConfigurationProperties.watchInitializationJson();
        }
        return watchInitializationJson;
    }

    /**
     * <p>If enabled the initialization json file will be watched for changes, any changes found will result in expectations being created, remove or updated by matching against their key.</p>
     * <p>If duplicate keys exist only the last duplicate key in the file will be processed and all duplicates except the last duplicate will be removed.</p>
     * <p>The order of expectations in the file is the order in which they are created if they are new, however, re-ordering existing expectations does not change the order they are matched against incoming requests.</p>
     *
     * <p>The default is false</p>
     *
     * @param watchInitializationJson if enabled the initialization json file will be watched for changes
     */
    public Configuration watchInitializationJson(Boolean watchInitializationJson) {
        this.watchInitializationJson = watchInitializationJson;
        return this;
    }

    public Boolean persistExpectations() {
        if (persistExpectations == null) {
            return ConfigurationProperties.persistExpectations();
        }
        return persistExpectations;
    }

    /**
     * Enable the persisting of expectations as json, which is updated whenever the expectation state is updated (i.e. add, clear, expires, etc.)
     * <p>
     * The default is false
     *
     * @param persistExpectations the persisting of expectations as json
     */
    public Configuration persistExpectations(Boolean persistExpectations) {
        this.persistExpectations = persistExpectations;
        return this;
    }

    public String persistedExpectationsPath() {
        if (persistedExpectationsPath == null) {
            return ConfigurationProperties.persistedExpectationsPath();
        }
        return persistedExpectationsPath;
    }

    /**
     * The file path used to save persisted expectations as json, which is updated whenever the expectation state is updated (i.e. add, clear, expires, etc.)
     * <p>
     * The default is "persistedExpectations.json"
     *
     * @param persistedExpectationsPath file path used to save persisted expectations as json
     */
    public Configuration persistedExpectationsPath(String persistedExpectationsPath) {
        this.persistedExpectationsPath = persistedExpectationsPath;
        return this;
    }

    public Boolean persistRecordedExpectations() {
        if (persistRecordedExpectations == null) {
            return ConfigurationProperties.persistRecordedExpectations();
        }
        return persistRecordedExpectations;
    }

    /**
     * Enable the persisting of recorded expectations (proxy traffic) as json, which is updated whenever a new request is forwarded
     * <p>
     * The default is false
     *
     * @param persistRecordedExpectations the persisting of recorded expectations as json
     */
    public Configuration persistRecordedExpectations(Boolean persistRecordedExpectations) {
        this.persistRecordedExpectations = persistRecordedExpectations;
        return this;
    }

    public String persistedRecordedExpectationsPath() {
        if (persistedRecordedExpectationsPath == null) {
            return ConfigurationProperties.persistedRecordedExpectationsPath();
        }
        return persistedRecordedExpectationsPath;
    }

    /**
     * The file path used to save persisted recorded expectations as json, which is updated whenever a new request is forwarded
     * <p>
     * The default is "persistedRecordedExpectations.json"
     *
     * @param persistedRecordedExpectationsPath file path used to save persisted recorded expectations as json
     */
    public Configuration persistedRecordedExpectationsPath(String persistedRecordedExpectationsPath) {
        this.persistedRecordedExpectationsPath = persistedRecordedExpectationsPath;
        return this;
    }

    public Integer maximumNumberOfRequestToReturnInVerificationFailure() {
        if (maximumNumberOfRequestToReturnInVerificationFailure == null) {
            return ConfigurationProperties.maximumNumberOfRequestToReturnInVerificationFailure();
        }
        return maximumNumberOfRequestToReturnInVerificationFailure;
    }

    /**
     * The maximum number of requests to return in verification failure result, if more expectations are found the failure result does not list them separately
     *
     * @param maximumNumberOfRequestToReturnInVerificationFailure maximum number of expectations to return in verification failure result
     */
    public Configuration maximumNumberOfRequestToReturnInVerificationFailure(Integer maximumNumberOfRequestToReturnInVerificationFailure) {
        this.maximumNumberOfRequestToReturnInVerificationFailure = maximumNumberOfRequestToReturnInVerificationFailure;
        return this;
    }

    public Boolean detailedVerificationFailures() {
        if (detailedVerificationFailures == null) {
            return ConfigurationProperties.detailedVerificationFailures();
        }
        return detailedVerificationFailures;
    }

    /**
     * If true (the default) verification failure messages include a detailed diff showing which fields did not match for the closest matching request.
     *
     * @param detailedVerificationFailures enabled detailed verification failure messages
     */
    public Configuration detailedVerificationFailures(Boolean detailedVerificationFailures) {
        this.detailedVerificationFailures = detailedVerificationFailures;
        return this;
    }

    public Boolean attemptToProxyIfNoMatchingExpectation() {
        if (attemptToProxyIfNoMatchingExpectation == null) {
            return ConfigurationProperties.attemptToProxyIfNoMatchingExpectation();
        }
        return attemptToProxyIfNoMatchingExpectation;
    }

    /**
     * If true (the default) when no matching expectation is found, and the host header of the request does not match MockServer's host, then MockServer attempts to proxy the request if that fails then a 404 is returned.
     * If false when no matching expectation is found, and MockServer is not being used as a proxy, then MockServer always returns a 404 immediately.
     *
     * @param attemptToProxyIfNoMatchingExpectation enables automatically attempted proxying of request that don't match an expectation and look like they should be proxied
     */
    public Configuration attemptToProxyIfNoMatchingExpectation(Boolean attemptToProxyIfNoMatchingExpectation) {
        this.attemptToProxyIfNoMatchingExpectation = attemptToProxyIfNoMatchingExpectation;
        return this;
    }

    public InetSocketAddress forwardHttpProxy() {
        if (forwardHttpProxy == null) {
            return ConfigurationProperties.forwardHttpProxy();
        }
        return forwardHttpProxy;
    }

    /**
     * Use HTTP proxy (i.e. via Host header) for all outbound / forwarded requests
     * <p>
     * The default is null
     *
     * @param forwardHttpProxy host and port for HTTP proxy (i.e. via Host header) for all outbound / forwarded requests
     */
    public Configuration forwardHttpProxy(InetSocketAddress forwardHttpProxy) {
        this.forwardHttpProxy = forwardHttpProxy;
        return this;
    }

    public InetSocketAddress forwardHttpsProxy() {
        if (forwardHttpsProxy == null) {
            return ConfigurationProperties.forwardHttpsProxy();
        }
        return forwardHttpsProxy;
    }

    /**
     * Use HTTPS proxy (i.e. HTTP CONNECT) for all outbound / forwarded requests, supports TLS tunnelling of HTTPS requests
     * <p>
     * The default is null
     *
     * @param forwardHttpsProxy host and port for HTTPS proxy (i.e. HTTP CONNECT) for all outbound / forwarded requests
     */
    public Configuration forwardHttpsProxy(InetSocketAddress forwardHttpsProxy) {
        this.forwardHttpsProxy = forwardHttpsProxy;
        return this;
    }

    public InetSocketAddress forwardSocksProxy() {
        if (forwardSocksProxy == null) {
            return ConfigurationProperties.forwardSocksProxy();
        }
        return forwardSocksProxy;
    }

    /**
     * Use SOCKS proxy for all outbound / forwarded requests, support TLS tunnelling of TCP connections
     * <p>
     * The default is null
     *
     * @param forwardSocksProxy host and port for SOCKS proxy for all outbound / forwarded requests
     */
    public Configuration forwardSocksProxy(InetSocketAddress forwardSocksProxy) {
        this.forwardSocksProxy = forwardSocksProxy;
        return this;
    }

    public String forwardProxyAuthenticationUsername() {
        if (forwardProxyAuthenticationUsername == null) {
            return ConfigurationProperties.forwardProxyAuthenticationUsername();
        }
        return forwardProxyAuthenticationUsername;
    }

    /**
     * <p>Username for proxy authentication when using HTTPS proxy (i.e. HTTP CONNECT) for all outbound / forwarded requests</p>
     * <p><strong>Note:</strong> <a target="_blank" href="https://www.oracle.com/java/technologies/javase/8u111-relnotes.html">8u111 Update Release Notes</a> state that the Basic authentication scheme has been deactivated when setting up an HTTPS tunnel.  To resolve this clear or set to an empty string the following system properties: <code class="inline code">jdk.http.auth.tunneling.disabledSchemes</code> and <code class="inline code">jdk.http.auth.proxying.disabledSchemes</code>.</p>
     * <p>
     * The default is null
     *
     * @param forwardProxyAuthenticationUsername username for proxy authentication
     */
    public Configuration forwardProxyAuthenticationUsername(String forwardProxyAuthenticationUsername) {
        this.forwardProxyAuthenticationUsername = forwardProxyAuthenticationUsername;
        return this;
    }

    public String forwardProxyAuthenticationPassword() {
        if (forwardProxyAuthenticationPassword == null) {
            return ConfigurationProperties.forwardProxyAuthenticationPassword();
        }
        return forwardProxyAuthenticationPassword;
    }

    /**
     * <p>Password for proxy authentication when using HTTPS proxy (i.e. HTTP CONNECT) for all outbound / forwarded requests</p>
     * <p><strong>Note:</strong> <a target="_blank" href="https://www.oracle.com/java/technologies/javase/8u111-relnotes.html">8u111 Update Release Notes</a> state that the Basic authentication scheme has been deactivated when setting up an HTTPS tunnel.  To resolve this clear or set to an empty string the following system properties: <code class="inline code">jdk.http.auth.tunneling.disabledSchemes</code> and <code class="inline code">jdk.http.auth.proxying.disabledSchemes</code>.</p>
     * <p>
     * The default is null
     *
     * @param forwardProxyAuthenticationPassword password for proxy authentication
     */
    public Configuration forwardProxyAuthenticationPassword(String forwardProxyAuthenticationPassword) {
        this.forwardProxyAuthenticationPassword = forwardProxyAuthenticationPassword;
        return this;
    }

    public String proxyAuthenticationRealm() {
        if (proxyAuthenticationRealm == null) {
            return ConfigurationProperties.proxyAuthenticationRealm();
        }
        return proxyAuthenticationRealm;
    }

    /**
     * The authentication realm for proxy authentication to MockServer
     *
     * @param proxyAuthenticationRealm the authentication realm for proxy authentication
     */
    public Configuration proxyAuthenticationRealm(String proxyAuthenticationRealm) {
        this.proxyAuthenticationRealm = proxyAuthenticationRealm;
        return this;
    }

    public String proxyAuthenticationUsername() {
        if (proxyAuthenticationUsername == null) {
            return ConfigurationProperties.proxyAuthenticationUsername();
        }
        return proxyAuthenticationUsername;
    }

    /**
     * <p>The required username for proxy authentication to MockServer</p>
     * <p><strong>Note:</strong> <a target="_blank" href="https://www.oracle.com/java/technologies/javase/8u111-relnotes.html">8u111 Update Release Notes</a> state that the Basic authentication scheme has been deactivated when setting up an HTTPS tunnel.  To resolve this clear or set to an empty string the following system properties: <code class="inline code">jdk.http.auth.tunneling.disabledSchemes</code> and <code class="inline code">jdk.http.auth.proxying.disabledSchemes</code>.</p>
     * <p>
     * The default is ""
     *
     * @param proxyAuthenticationUsername required username for proxy authentication to MockServer
     */
    public Configuration proxyAuthenticationUsername(String proxyAuthenticationUsername) {
        this.proxyAuthenticationUsername = proxyAuthenticationUsername;
        return this;
    }

    public String proxyAuthenticationPassword() {
        if (proxyAuthenticationPassword == null) {
            return ConfigurationProperties.proxyAuthenticationPassword();
        }
        return proxyAuthenticationPassword;
    }

    /**
     * <p>The required password for proxy authentication to MockServer</p>
     * <p><strong>Note:</strong> <a target="_blank" href="https://www.oracle.com/java/technologies/javase/8u111-relnotes.html">8u111 Update Release Notes</a> state that the Basic authentication scheme has been deactivated when setting up an HTTPS tunnel.  To resolve this clear or set to an empty string the following system properties: <code class="inline code">jdk.http.auth.tunneling.disabledSchemes</code> and <code class="inline code">jdk.http.auth.proxying.disabledSchemes</code>.</p>
     * <p>
     * The default is ""
     *
     * @param proxyAuthenticationPassword required password for proxy authentication to MockServer
     */
    public Configuration proxyAuthenticationPassword(String proxyAuthenticationPassword) {
        this.proxyAuthenticationPassword = proxyAuthenticationPassword;
        return this;
    }

    public String noProxyHosts() {
        if (noProxyHosts == null) {
            return ConfigurationProperties.noProxyHosts();
        }
        return noProxyHosts;
    }

    /**
     * <p>The list of hostnames to not use the configured proxy. Several values may be present, seperated by comma (,)</p>
     * The default is ""
     *
     * @param noProxyHosts Comma-seperated list of hosts to not be proxied.
     */
    public Configuration noProxyHosts(String noProxyHosts) {
        this.noProxyHosts = noProxyHosts;
        return this;
    }

    public String proxyRemoteHost() {
        if (proxyRemoteHost == null) {
            return ConfigurationProperties.proxyRemoteHost();
        }
        return proxyRemoteHost;
    }

    /**
     * The hostname of the remote server to proxy all requests to.
     * When set, unmatched requests are forwarded to this host.
     *
     * @param proxyRemoteHost the hostname to forward requests to
     */
    public Configuration proxyRemoteHost(String proxyRemoteHost) {
        this.proxyRemoteHost = proxyRemoteHost;
        return this;
    }

    public Integer proxyRemotePort() {
        if (proxyRemotePort == null) {
            return ConfigurationProperties.proxyRemotePort();
        }
        return proxyRemotePort;
    }

    /**
     * The port of the remote server to proxy all requests to.
     * Must be specified together with proxyRemoteHost.
     *
     * @param proxyRemotePort the port to forward requests to
     */
    public Configuration proxyRemotePort(Integer proxyRemotePort) {
        this.proxyRemotePort = proxyRemotePort;
        return this;
    }

    public Boolean forwardAdjustHostHeader() {
        if (forwardAdjustHostHeader == null) {
            return ConfigurationProperties.forwardAdjustHostHeader();
        }
        return forwardAdjustHostHeader;
    }

    /**
     * If true (the default) the Host header will be automatically adjusted to match the target server when forwarding requests.
     * This prevents HTTP 421 Misdirected Request errors when the target server validates Host headers.
     * If false the original Host header is preserved.
     *
     * @param forwardAdjustHostHeader enables automatic Host header adjustment for forwarded requests
     */
    public Configuration forwardAdjustHostHeader(Boolean forwardAdjustHostHeader) {
        this.forwardAdjustHostHeader = forwardAdjustHostHeader;
        return this;
    }

    public String forwardDefaultHostHeader() {
        if (forwardDefaultHostHeader == null) {
            return ConfigurationProperties.forwardDefaultHostHeader();
        }
        return forwardDefaultHostHeader;
    }

    /**
     * Set a default Host header value to use when forwarding requests.
     * When set, the Host header will be overridden with this value for all forwarded requests,
     * regardless of the target server's address. This is useful when the target server
     * routes requests based on the Host header.
     *
     * @param forwardDefaultHostHeader the Host header value to set on forwarded requests
     */
    public Configuration forwardDefaultHostHeader(String forwardDefaultHostHeader) {
        this.forwardDefaultHostHeader = forwardDefaultHostHeader;
        return this;
    }

    public List<ProxyPassMapping> proxyPassMappings() {
        if (proxyPassMappings == null) {
            return ConfigurationProperties.proxyPass();
        }
        return proxyPassMappings;
    }

    /**
     * Configure ProxyPass mappings that map incoming path prefixes to upstream servers with automatic path rewriting.
     *
     * @param proxyPassMappings list of ProxyPassMapping objects
     */
    public Configuration proxyPassMappings(List<ProxyPassMapping> proxyPassMappings) {
        this.proxyPassMappings = proxyPassMappings;
        return this;
    }

    public Long globalResponseDelayMillis() {
        if (globalResponseDelayMillis == null) {
            return ConfigurationProperties.globalResponseDelayMillis();
        }
        return globalResponseDelayMillis;
    }

    public Configuration globalResponseDelayMillis(Long globalResponseDelayMillis) {
        if (globalResponseDelayMillis != null && globalResponseDelayMillis < 0) {
            throw new IllegalArgumentException("globalResponseDelayMillis must be >= 0, got: " + globalResponseDelayMillis);
        }
        this.globalResponseDelayMillis = globalResponseDelayMillis;
        return this;
    }

    public String livenessHttpGetPath() {
        if (livenessHttpGetPath == null) {
            return ConfigurationProperties.livenessHttpGetPath();
        }
        return livenessHttpGetPath;
    }

    /**
     * Path to support HTTP GET requests for status response (also available on PUT /mockserver/status).
     * <p>
     * If this value is not modified then only PUT /mockserver/status but is a none blank value is provided for this value then GET requests to this path will return the 200 Ok status response showing the MockServer version and bound ports.
     * <p>
     * A GET request to this path will be matched before any expectation matching or proxying of requests.
     * <p>
     * The default is ""
     *
     * @param livenessHttpGetPath path to support HTTP GET requests for status response
     */
    public Configuration livenessHttpGetPath(String livenessHttpGetPath) {
        this.livenessHttpGetPath = livenessHttpGetPath;
        return this;
    }

    public Boolean controlPlaneTLSMutualAuthenticationRequired() {
        if (controlPlaneTLSMutualAuthenticationRequired == null) {
            return ConfigurationProperties.controlPlaneTLSMutualAuthenticationRequired();
        }
        return controlPlaneTLSMutualAuthenticationRequired;
    }

    /**
     * Require mTLS (also called client authentication and two-way TLS) for all control plane requests
     *
     * @param controlPlaneTLSMutualAuthenticationRequired TLS mutual authentication for all control plane requests
     */
    public Configuration controlPlaneTLSMutualAuthenticationRequired(Boolean controlPlaneTLSMutualAuthenticationRequired) {
        this.controlPlaneTLSMutualAuthenticationRequired = controlPlaneTLSMutualAuthenticationRequired;
        return this;
    }

    public String controlPlaneTLSMutualAuthenticationCAChain() {
        if (controlPlaneTLSMutualAuthenticationCAChain == null) {
            return ConfigurationProperties.controlPlaneTLSMutualAuthenticationCAChain();
        }
        return controlPlaneTLSMutualAuthenticationCAChain;
    }

    /**
     * File system path or classpath location of custom mTLS (TLS client authentication) X.509 Certificate Chain for control plane mTLS authentication
     * <p>
     * The X.509 Certificate Chain is for trusting (i.e. signature verification of) Client X.509 Certificates, the certificate chain must be a X509 PEM file.
     * <p>
     * This certificate chain will be used for to performs mTLS (client authentication) for inbound TLS connections if controlPlaneTLSMutualAuthenticationRequired is enabled
     *
     * @param controlPlaneTLSMutualAuthenticationCAChain File system path or classpath location of custom mTLS (TLS client authentication) X.509 Certificate Chain for Trusting (i.e. signature verification of) Client X.509 Certificates
     */
    public Configuration controlPlaneTLSMutualAuthenticationCAChain(String controlPlaneTLSMutualAuthenticationCAChain) {
        fileExists(controlPlaneTLSMutualAuthenticationCAChain);
        this.controlPlaneTLSMutualAuthenticationCAChain = controlPlaneTLSMutualAuthenticationCAChain;
        return this;
    }

    public String controlPlanePrivateKeyPath() {
        if (controlPlanePrivateKeyPath == null) {
            return ConfigurationProperties.controlPlanePrivateKeyPath();
        }
        return controlPlanePrivateKeyPath;
    }

    /**
     * File system path or classpath location of a fixed custom private key for control plane connections using mTLS for authentication.
     * <p>
     * The private key must be a PKCS#8 or PKCS#1 PEM file and must be the private key corresponding to the controlPlaneX509CertificatePath X509 (public key) configuration.
     * The controlPlaneTLSMutualAuthenticationCAChain configuration must be the Certificate Authority for the corresponding X509 certificate (i.e. able to valid its signature).
     * <p>
     * To convert a PKCS#1 (i.e. default for Bouncy Castle) to a PKCS#8 the following command can be used: openssl pkcs8 -topk8 -inform PEM -in private_key_PKCS_1.pem -out private_key_PKCS_8.pem -nocrypt
     * <p>
     * This configuration will be ignored unless x509CertificatePath is also set.
     *
     * @param controlPlanePrivateKeyPath location of the PKCS#8 PEM file containing the private key
     */
    public Configuration controlPlanePrivateKeyPath(String controlPlanePrivateKeyPath) {
        fileExists(controlPlanePrivateKeyPath);
        this.controlPlanePrivateKeyPath = controlPlanePrivateKeyPath;
        return this;
    }

    public String controlPlaneX509CertificatePath() {
        if (controlPlaneX509CertificatePath == null) {
            return ConfigurationProperties.controlPlaneX509CertificatePath();
        }
        return controlPlaneX509CertificatePath;
    }

    /**
     * File system path or classpath location of a fixed custom X.509 Certificate for control plane connections using mTLS for authentication.
     * <p>
     * The certificate must be a X509 PEM file and must be the public key corresponding to the controlPlanePrivateKeyPath private key configuration.
     * The controlPlaneTLSMutualAuthenticationCAChain configuration must be the Certificate Authority for this certificate (i.e. able to valid its signature).
     * <p>
     * This configuration will be ignored unless privateKeyPath is also set.
     *
     * @param controlPlaneX509CertificatePath location of the PEM file containing the X509 certificate
     */
    public Configuration controlPlaneX509CertificatePath(String controlPlaneX509CertificatePath) {
        fileExists(controlPlaneX509CertificatePath);
        this.controlPlaneX509CertificatePath = controlPlaneX509CertificatePath;
        return this;
    }

    public Boolean controlPlaneJWTAuthenticationRequired() {
        if (controlPlaneJWTAuthenticationRequired == null) {
            return ConfigurationProperties.controlPlaneJWTAuthenticationRequired();
        }
        return controlPlaneJWTAuthenticationRequired;
    }

    /**
     * <p>
     * Require JWT authentication for all control plane requests
     * </p>
     *
     * @param controlPlaneJWTAuthenticationRequired TLS mutual authentication for all control plane requests
     */
    public Configuration controlPlaneJWTAuthenticationRequired(Boolean controlPlaneJWTAuthenticationRequired) {
        this.controlPlaneJWTAuthenticationRequired = controlPlaneJWTAuthenticationRequired;
        return this;
    }

    public String controlPlaneJWTAuthenticationJWKSource() {
        if (controlPlaneJWTAuthenticationJWKSource == null) {
            return ConfigurationProperties.controlPlaneJWTAuthenticationJWKSource();
        }
        return controlPlaneJWTAuthenticationJWKSource;
    }

    /**
     * <p>
     * JWK source used when JWT authentication is enabled for control plane requests
     * </p>
     * <p>
     * JWK source can be a file system path, classpath location or a URL
     * </p>
     * <p>
     * See: https://openid.net/specs/draft-jones-json-web-key-03.html
     * </p>
     *
     * @param controlPlaneJWTAuthenticationJWKSource file system path, classpath location or a URL of JWK source
     */
    public Configuration controlPlaneJWTAuthenticationJWKSource(String controlPlaneJWTAuthenticationJWKSource) {
        this.controlPlaneJWTAuthenticationJWKSource = controlPlaneJWTAuthenticationJWKSource;
        return this;
    }

    public String controlPlaneJWTAuthenticationExpectedAudience() {
        if (controlPlaneJWTAuthenticationExpectedAudience == null) {
            return ConfigurationProperties.controlPlaneJWTAuthenticationExpectedAudience();
        }
        return controlPlaneJWTAuthenticationExpectedAudience;
    }

    /**
     * <p>
     * Audience claim (i.e. aud) required when JWT authentication is enabled for control plane requests
     * </p>
     *
     * @param controlPlaneJWTAuthenticationExpectedAudience required value for audience claim (i.e. aud)
     */
    public Configuration controlPlaneJWTAuthenticationExpectedAudience(String controlPlaneJWTAuthenticationExpectedAudience) {
        this.controlPlaneJWTAuthenticationExpectedAudience = controlPlaneJWTAuthenticationExpectedAudience;
        return this;
    }

    public Map<String, String> controlPlaneJWTAuthenticationMatchingClaims() {
        if (controlPlaneJWTAuthenticationMatchingClaims == null) {
            return ConfigurationProperties.controlPlaneJWTAuthenticationMatchingClaims();
        }
        return controlPlaneJWTAuthenticationMatchingClaims;
    }

    /**
     * <p>
     * Matching claims expected when JWT authentication is enabled for control plane requests
     * </p>
     * <p>
     * Value should be string with comma separated key=value items, for example: scope=internal public,sub=some_subject
     * </p>
     *
     * @param controlPlaneJWTAuthenticationMatchingClaims required values for claims
     */
    public Configuration controlPlaneJWTAuthenticationMatchingClaims(Map<String, String> controlPlaneJWTAuthenticationMatchingClaims) {
        this.controlPlaneJWTAuthenticationMatchingClaims = controlPlaneJWTAuthenticationMatchingClaims;
        return this;
    }

    public Set<String> controlPlaneJWTAuthenticationRequiredClaims() {
        if (controlPlaneJWTAuthenticationRequiredClaims == null) {
            return ConfigurationProperties.controlPlaneJWTAuthenticationRequiredClaims();
        }
        return controlPlaneJWTAuthenticationRequiredClaims;
    }

    /**
     * <p>
     * Required claims that should exist (i.e. with any value) when JWT authentication is enabled for control plane requests
     * </p>
     * <p>
     * Value should be string with comma separated values, for example: scope,sub
     * </p>
     *
     * @param controlPlaneJWTAuthenticationRequiredClaims required claims
     */
    public Configuration controlPlaneJWTAuthenticationRequiredClaims(Set<String> controlPlaneJWTAuthenticationRequiredClaims) {
        this.controlPlaneJWTAuthenticationRequiredClaims = controlPlaneJWTAuthenticationRequiredClaims;
        return this;
    }

    public Boolean proactivelyInitialiseTLS() {
        if (proactivelyInitialiseTLS == null) {
            return ConfigurationProperties.proactivelyInitialiseTLS();
        }
        return proactivelyInitialiseTLS;
    }

    /**
     * <p>Proactively initialise TLS during start to ensure that if dynamicallyCreateCertificateAuthorityCertificate is enabled the Certificate Authority X.509 Certificate and Private Key will be created during start up and not when the first TLS connection is received.</p>
     * <p>This setting will also ensure any configured private key and X.509 will be loaded during start up and not when the first TLS connection is received to give immediate feedback on any related TLS configuration errors.</p>
     *
     * @param proactivelyInitialiseTLS proactively initialise TLS at startup
     */
    public Configuration proactivelyInitialiseTLS(Boolean proactivelyInitialiseTLS) {
        this.proactivelyInitialiseTLS = proactivelyInitialiseTLS;
        return this;
    }

    public boolean rebuildTLSContext() {
        return rebuildTLSContext;
    }

    public Configuration rebuildTLSContext(boolean rebuildTLSContext) {
        this.rebuildTLSContext = rebuildTLSContext;
        return this;
    }

    public boolean rebuildServerTLSContext() {
        return rebuildServerTLSContext;
    }

    public Configuration rebuildServerTLSContext(boolean rebuildServerTLSContext) {
        this.rebuildServerTLSContext = rebuildServerTLSContext;
        return this;
    }

    public String tlsProtocols() {
        if (tlsProtocols == null) {
            return ConfigurationProperties.tlsProtocols();
        }
        return tlsProtocols;
    }

    /**
     * Comma seperated list of TLS protocols, by default TLSv1,TLSv1.1,TLSv1.2
     *
     * @param tlsProtocols comma seperated list of TLS protocols
     */
    public Configuration tlsProtocols(String tlsProtocols) {
        this.tlsProtocols = tlsProtocols;
        return this;
    }

    public Boolean tlsAllowInsecureProtocols() {
        if (tlsAllowInsecureProtocols == null) {
            return ConfigurationProperties.tlsAllowInsecureProtocols();
        }
        return tlsAllowInsecureProtocols;
    }

    /**
     * Whether to allow TLSv1 and TLSv1.1 in the effective TLS protocols list.
     * Both are deprecated by RFC 8996 and vulnerable to BEAST and POODLE.
     * The default is true for backwards compatibility; set to false to opt into
     * a hardened profile that filters TLSv1 and TLSv1.1 out of {@link #tlsProtocols}.
     *
     * @param tlsAllowInsecureProtocols if true, TLSv1 and TLSv1.1 are honoured; if false, they are stripped
     */
    public Configuration tlsAllowInsecureProtocols(Boolean tlsAllowInsecureProtocols) {
        this.tlsAllowInsecureProtocols = tlsAllowInsecureProtocols;
        return this;
    }

    public Boolean dynamicallyCreateCertificateAuthorityCertificate() {
        if (dynamicallyCreateCertificateAuthorityCertificate == null) {
            return ConfigurationProperties.dynamicallyCreateCertificateAuthorityCertificate();
        }
        return dynamicallyCreateCertificateAuthorityCertificate;
    }

    /**
     * Enable dynamic creation of Certificate Authority X509 certificate and private key.
     * <p>
     * Enable this property to increase the security of trusting the MockServer Certificate Authority X509 by ensuring a local dynamic value is used instead of the public value in the MockServer git repo.
     * <p>
     * These PEM files will be created and saved in the directory specified with configuration property directoryToSaveDynamicSSLCertificate.
     *
     * @param dynamicallyCreateCertificateAuthorityCertificate dynamic creation of Certificate Authority X509 certificate and private key.
     */
    public Configuration dynamicallyCreateCertificateAuthorityCertificate(Boolean dynamicallyCreateCertificateAuthorityCertificate) {
        this.dynamicallyCreateCertificateAuthorityCertificate = dynamicallyCreateCertificateAuthorityCertificate;
        return this;
    }

    public String directoryToSaveDynamicSSLCertificate() {
        if (directoryToSaveDynamicSSLCertificate == null) {
            return ConfigurationProperties.directoryToSaveDynamicSSLCertificate();
        }
        return directoryToSaveDynamicSSLCertificate;
    }

    /**
     * Directory used to save the dynamically generated Certificate Authority X.509 Certificate and Private Key.
     *
     * @param directoryToSaveDynamicSSLCertificate directory to save Certificate Authority X.509 Certificate and Private Key
     */
    public Configuration directoryToSaveDynamicSSLCertificate(String directoryToSaveDynamicSSLCertificate) {
        this.directoryToSaveDynamicSSLCertificate = directoryToSaveDynamicSSLCertificate;
        return this;
    }

    public Boolean preventCertificateDynamicUpdate() {
        if (preventCertificateDynamicUpdate == null) {
            return ConfigurationProperties.preventCertificateDynamicUpdate();
        }
        return preventCertificateDynamicUpdate;
    }

    /**
     * Prevent certificates from dynamically updating when domain list changes
     *
     * @param preventCertificateDynamicUpdate prevent certificates from dynamically updating when domain list changes
     */
    public Configuration preventCertificateDynamicUpdate(Boolean preventCertificateDynamicUpdate) {
        this.preventCertificateDynamicUpdate = preventCertificateDynamicUpdate;
        return this;
    }

    public String sslCertificateDomainName() {
        if (sslCertificateDomainName == null) {
            return ConfigurationProperties.sslCertificateDomainName();
        }
        return sslCertificateDomainName;
    }

    /**
     * The domain name for auto-generate TLS certificates
     * <p>
     * The default is "localhost"
     *
     * @param sslCertificateDomainName domain name for auto-generate TLS certificates
     */
    public Configuration sslCertificateDomainName(String sslCertificateDomainName) {
        this.sslCertificateDomainName = sslCertificateDomainName;
        return this;
    }

    public Set<String> sslSubjectAlternativeNameDomains() {
        if (sslSubjectAlternativeNameDomains == null) {
            return ConfigurationProperties.sslSubjectAlternativeNameDomains();
        }
        return sslSubjectAlternativeNameDomains;
    }

    /**
     * The Subject Alternative Name (SAN) domain names for auto-generate TLS certificates
     * <p>
     * The default is "localhost"
     *
     * @param sslSubjectAlternativeNameDomains Subject Alternative Name (SAN) domain names for auto-generate TLS certificates
     */
    public Configuration sslSubjectAlternativeNameDomains(String... sslSubjectAlternativeNameDomains) {
        this.sslSubjectAlternativeNameDomains = Sets.newConcurrentHashSet(Arrays.asList(sslSubjectAlternativeNameDomains));
        return this;
    }

    /**
     * The Subject Alternative Name (SAN) domain names for auto-generate TLS certificates
     * <p>
     * The default is "localhost"
     *
     * @param sslSubjectAlternativeNameDomains Subject Alternative Name (SAN) domain names for auto-generate TLS certificates
     */
    public Configuration sslSubjectAlternativeNameDomains(Set<String> sslSubjectAlternativeNameDomains) {
        this.sslSubjectAlternativeNameDomains = sslSubjectAlternativeNameDomains;
        return this;
    }

    public Set<String> sslSubjectAlternativeNameIps() {
        if (sslSubjectAlternativeNameIps == null) {
            return ConfigurationProperties.sslSubjectAlternativeNameIps();
        }
        return sslSubjectAlternativeNameIps;
    }

    /**
     * <p>The Subject Alternative Name (SAN) IP addresses for auto-generate TLS certificates</p>
     *
     * <p>The default is 127.0.0.1, 0.0.0.0</p>
     *
     * @param sslSubjectAlternativeNameIps Subject Alternative Name (SAN) IP addresses for auto-generate TLS certificates
     */
    public Configuration sslSubjectAlternativeNameIps(String... sslSubjectAlternativeNameIps) {
        sslSubjectAlternativeNameIps(Sets.newConcurrentHashSet(Arrays.asList(sslSubjectAlternativeNameIps)));
        return this;
    }

    /**
     * <p>The Subject Alternative Name (SAN) IP addresses for auto-generate TLS certificates</p>
     *
     * <p>The default is 127.0.0.1, 0.0.0.0</p>
     *
     * @param sslSubjectAlternativeNameIps Subject Alternative Name (SAN) IP addresses for auto-generate TLS certificates
     */
    public Configuration sslSubjectAlternativeNameIps(Set<String> sslSubjectAlternativeNameIps) {
        this.sslSubjectAlternativeNameIps = sslSubjectAlternativeNameIps;
        return this;
    }

    public String certificateAuthorityPrivateKey() {
        if (certificateAuthorityPrivateKey == null) {
            return ConfigurationProperties.certificateAuthorityPrivateKey();
        }
        return certificateAuthorityPrivateKey;
    }

    /**
     * File system path or classpath location of custom Private Key for Certificate Authority for TLS, the private key must be a PKCS#8 or PKCS#1 PEM file and must match the certificateAuthorityCertificate
     * To convert a PKCS#1 (i.e. default for Bouncy Castle) to a PKCS#8 the following command can be used: openssl pkcs8 -topk8 -inform PEM -in private_key_PKCS_1.pem -out private_key_PKCS_8.pem -nocrypt
     * <p>
     * The path is not file-existence-checked here because dynamic CA generation
     * ({@link #dynamicallyCreateCertificateAuthorityCertificate}) sets this to the
     * destination path before the file is written. Typos in user-supplied paths are
     * surfaced by {@link org.mockserver.socket.tls.CertificateConfigurationValidator}
     * at TLS-init time.
     *
     * @param certificateAuthorityPrivateKey location of the PEM file containing the certificate authority private key
     */
    public Configuration certificateAuthorityPrivateKey(String certificateAuthorityPrivateKey) {
        this.certificateAuthorityPrivateKey = certificateAuthorityPrivateKey;
        return this;
    }

    public String certificateAuthorityCertificate() {
        if (certificateAuthorityCertificate == null) {
            return ConfigurationProperties.certificateAuthorityCertificate();
        }
        return certificateAuthorityCertificate;
    }

    /**
     * File system path or classpath location of custom X.509 Certificate for Certificate Authority for TLS, the certificate must be a X509 PEM file and must match the certificateAuthorityPrivateKey
     * <p>
     * The path is not file-existence-checked here because dynamic CA generation
     * ({@link #dynamicallyCreateCertificateAuthorityCertificate}) sets this to the
     * destination path before the file is written. Typos in user-supplied paths are
     * surfaced by {@link org.mockserver.socket.tls.CertificateConfigurationValidator}
     * at TLS-init time.
     *
     * @param certificateAuthorityCertificate location of the PEM file containing the certificate authority X509 certificate
     */
    public Configuration certificateAuthorityCertificate(String certificateAuthorityCertificate) {
        this.certificateAuthorityCertificate = certificateAuthorityCertificate;
        return this;
    }

    public String privateKeyPath() {
        if (privateKeyPath == null) {
            return ConfigurationProperties.privateKeyPath();
        }
        return privateKeyPath;
    }

    /**
     * File system path or classpath location of a fixed custom private key for TLS connections into MockServer.
     * <p>
     * The private key must be a PKCS#8 or PKCS#1 PEM file and must be the private key corresponding to the x509CertificatePath X509 (public key) configuration.
     * The certificateAuthorityCertificate configuration must be the Certificate Authority for the corresponding X509 certificate (i.e. able to valid its signature), see: x509CertificatePath.
     * <p>
     * To convert a PKCS#1 (i.e. default for Bouncy Castle) to a PKCS#8 the following command can be used: openssl pkcs8 -topk8 -inform PEM -in private_key_PKCS_1.pem -out private_key_PKCS_8.pem -nocrypt
     * <p>
     * This configuration will be ignored unless x509CertificatePath is also set.
     * <p>
     * The path is not file-existence-checked here because dynamic SSL certificate
     * generation sets this to the destination path before the file is written. Typos
     * in user-supplied paths are surfaced by
     * {@link org.mockserver.socket.tls.CertificateConfigurationValidator} at TLS-init time.
     *
     * @param privateKeyPath location of the PKCS#8 PEM file containing the private key
     */
    public Configuration privateKeyPath(String privateKeyPath) {
        this.privateKeyPath = privateKeyPath;
        return this;
    }

    public String x509CertificatePath() {
        if (x509CertificatePath == null) {
            return ConfigurationProperties.x509CertificatePath();
        }
        return x509CertificatePath;
    }

    /**
     * File system path or classpath location of a fixed custom X.509 Certificate for TLS connections into MockServer.
     * <p>
     * The certificate must be a X509 PEM file and must be the public key corresponding to the privateKeyPath private key configuration.
     * The certificateAuthorityCertificate configuration must be the Certificate Authority for this certificate (i.e. able to valid its signature).
     * <p>
     * This configuration will be ignored unless privateKeyPath is also set.
     * <p>
     * The path is not file-existence-checked here because dynamic SSL certificate
     * generation sets this to the destination path before the file is written. Typos
     * in user-supplied paths are surfaced by
     * {@link org.mockserver.socket.tls.CertificateConfigurationValidator} at TLS-init time.
     *
     * @param x509CertificatePath location of the PEM file containing the X509 certificate
     */
    public Configuration x509CertificatePath(String x509CertificatePath) {
        this.x509CertificatePath = x509CertificatePath;
        return this;
    }

    public Boolean tlsMutualAuthenticationRequired() {
        if (tlsMutualAuthenticationRequired == null) {
            return ConfigurationProperties.tlsMutualAuthenticationRequired();
        }
        return tlsMutualAuthenticationRequired;
    }

    /**
     * Require mTLS (also called client authentication and two-way TLS) for all TLS connections / HTTPS requests to MockServer
     *
     * @param tlsMutualAuthenticationRequired TLS mutual authentication
     */
    public Configuration tlsMutualAuthenticationRequired(Boolean tlsMutualAuthenticationRequired) {
        this.tlsMutualAuthenticationRequired = tlsMutualAuthenticationRequired;
        return this;
    }

    public String tlsMutualAuthenticationCertificateChain() {
        if (tlsMutualAuthenticationCertificateChain == null) {
            return ConfigurationProperties.tlsMutualAuthenticationCertificateChain();
        }
        return tlsMutualAuthenticationCertificateChain;
    }

    /**
     * File system path or classpath location of custom mTLS (TLS client authentication) X.509 Certificate Chain for trusting (i.e. signature verification of) Client X.509 Certificates, the certificate chain must be a X509 PEM file.
     * <p>
     * This certificate chain will be used if MockServer performs mTLS (client authentication) for inbound TLS connections because tlsMutualAuthenticationRequired is enabled
     *
     * @param tlsMutualAuthenticationCertificateChain File system path or classpath location of custom mTLS (TLS client authentication) X.509 Certificate Chain for Trusting (i.e. signature verification of) Client X.509 Certificates
     */
    public Configuration tlsMutualAuthenticationCertificateChain(String tlsMutualAuthenticationCertificateChain) {
        fileExists(tlsMutualAuthenticationCertificateChain);
        this.tlsMutualAuthenticationCertificateChain = tlsMutualAuthenticationCertificateChain;
        return this;
    }

    public ForwardProxyTLSX509CertificatesTrustManager forwardProxyTLSX509CertificatesTrustManagerType() {
        if (forwardProxyTLSX509CertificatesTrustManagerType == null) {
            return ConfigurationProperties.forwardProxyTLSX509CertificatesTrustManagerType();
        }
        return forwardProxyTLSX509CertificatesTrustManagerType;
    }

    /**
     * Configure trusted set of certificates for forwarded or proxied requests.
     * <p>
     * MockServer will only be able to establish a TLS connection to endpoints that have a trusted X509 certificate according to the trust manager type, as follows:
     * <p>
     * <p>
     * ALL - Insecure will trust all X509 certificates and not perform host name verification.
     * JVM - Will trust all X509 certificates trust by the JVM.
     * CUSTOM - Will trust all X509 certificates specified in forwardProxyTLSCustomTrustX509Certificates configuration value.
     *
     * @param forwardProxyTLSX509CertificatesTrustManagerType trusted set of certificates for forwarded or proxied requests, allowed values: ALL, JVM, CUSTOM.
     */
    public Configuration forwardProxyTLSX509CertificatesTrustManagerType(ForwardProxyTLSX509CertificatesTrustManager forwardProxyTLSX509CertificatesTrustManagerType) {
        this.forwardProxyTLSX509CertificatesTrustManagerType = forwardProxyTLSX509CertificatesTrustManagerType;
        return this;
    }

    public Boolean forwardProxyBlockPrivateNetworks() {
        if (forwardProxyBlockPrivateNetworks == null) {
            return ConfigurationProperties.forwardProxyBlockPrivateNetworks();
        }
        return forwardProxyBlockPrivateNetworks;
    }

    /**
     * When set to true, MockServer rejects forward and proxy targets that resolve to
     * loopback, link-local, RFC 1918 private, or cloud metadata addresses
     * (such as 169.254.169.254), blocking server-side request forgery (SSRF) via
     * malicious expectations.
     * <p>
     * The default is false so that the common case of forwarding to localhost / Docker
     * bridge / Kubernetes service IPs continues to work. Enable this in hardened or
     * multi-tenant deployments where untrusted callers can register expectations.
     *
     * @param forwardProxyBlockPrivateNetworks if true, block forwarding to private or metadata addresses
     */
    public Configuration forwardProxyBlockPrivateNetworks(Boolean forwardProxyBlockPrivateNetworks) {
        this.forwardProxyBlockPrivateNetworks = forwardProxyBlockPrivateNetworks;
        return this;
    }

    public String forwardProxyTLSCustomTrustX509Certificates() {
        if (forwardProxyTLSCustomTrustX509Certificates == null) {
            return ConfigurationProperties.forwardProxyTLSCustomTrustX509Certificates();
        }
        return forwardProxyTLSCustomTrustX509Certificates;
    }

    /**
     * File system path or classpath location of custom file for trusted X509 Certificate Authority roots for forwarded or proxied requests, the certificate chain must be a X509 PEM file.
     * <p>
     * MockServer will only be able to establish a TLS connection to endpoints that have an X509 certificate chain that is signed by one of the provided custom
     * certificates, i.e. where a path can be established from the endpoints X509 certificate to one or more of the custom X509 certificates provided.
     *
     * @param forwardProxyTLSCustomTrustX509Certificates custom set of trusted X509 certificate authority roots for forwarded or proxied requests in PEM format.
     */
    public Configuration forwardProxyTLSCustomTrustX509Certificates(String forwardProxyTLSCustomTrustX509Certificates) {
        fileExists(forwardProxyTLSCustomTrustX509Certificates);
        this.forwardProxyTLSCustomTrustX509Certificates = forwardProxyTLSCustomTrustX509Certificates;
        return this;
    }

    public String forwardProxyPrivateKey() {
        if (forwardProxyPrivateKey == null) {
            return ConfigurationProperties.forwardProxyPrivateKey();
        }
        return forwardProxyPrivateKey;
    }

    /**
     * File system path or classpath location of custom Private Key for proxied TLS connections out of MockServer, the private key must be a PKCS#8 or PKCS#1 PEM file
     * <p>
     * To convert a PKCS#1 (i.e. default for Bouncy Castle) to a PKCS#8 the following command can be used: openssl pkcs8 -topk8 -inform PEM -in private_key_PKCS_1.pem -out private_key_PKCS_8.pem -nocrypt
     * <p>
     * This private key will be used if MockServer needs to perform mTLS (client authentication) for outbound TLS connections.
     *
     * @param forwardProxyPrivateKey location of the PEM file containing the private key
     */
    public Configuration forwardProxyPrivateKey(String forwardProxyPrivateKey) {
        fileExists(forwardProxyPrivateKey);
        this.forwardProxyPrivateKey = forwardProxyPrivateKey;
        return this;
    }

    public String forwardProxyCertificateChain() {
        if (forwardProxyCertificateChain == null) {
            return ConfigurationProperties.forwardProxyCertificateChain();
        }
        return forwardProxyCertificateChain;
    }

    /**
     * File system path or classpath location of custom mTLS (TLS client authentication) X.509 Certificate Chain for Trusting (i.e. signature verification of) Client X.509 Certificates, the certificate chain must be a X509 PEM file.
     * <p>
     * This certificate chain will be used if MockServer needs to perform mTLS (client authentication) for outbound TLS connections.
     *
     * @param forwardProxyCertificateChain location of the PEM file containing the certificate chain
     */
    public Configuration forwardProxyCertificateChain(String forwardProxyCertificateChain) {
        fileExists(forwardProxyCertificateChain);
        this.forwardProxyCertificateChain = forwardProxyCertificateChain;
        return this;
    }

    public Boolean transparentProxyEnabled() {
        if (transparentProxyEnabled == null) {
            return ConfigurationProperties.transparentProxyEnabled();
        }
        return transparentProxyEnabled;
    }

    /**
     * Enable transparent HTTP proxy mode where all connections are treated as proxy
     * requests using the Host header as the forwarding target. This enables
     * iptables REDIRECT-based interception without CONNECT.
     * <p>
     * The default is false
     *
     * @param transparentProxyEnabled enable transparent proxy mode
     */
    public Configuration transparentProxyEnabled(Boolean transparentProxyEnabled) {
        this.transparentProxyEnabled = transparentProxyEnabled;
        return this;
    }

    // async messaging defaults

    public String asyncKafkaBootstrapServers() {
        if (asyncKafkaBootstrapServers == null) {
            return ConfigurationProperties.asyncKafkaBootstrapServers();
        }
        return asyncKafkaBootstrapServers;
    }

    /**
     * Default Kafka bootstrap servers for async messaging. Used when a
     * {@code PUT /mockserver/asyncapi} request omits {@code brokerConfig.kafkaBootstrapServers}.
     *
     * @param asyncKafkaBootstrapServers the default Kafka bootstrap servers
     */
    public Configuration asyncKafkaBootstrapServers(String asyncKafkaBootstrapServers) {
        this.asyncKafkaBootstrapServers = asyncKafkaBootstrapServers;
        return this;
    }

    public String asyncMqttBrokerUrl() {
        if (asyncMqttBrokerUrl == null) {
            return ConfigurationProperties.asyncMqttBrokerUrl();
        }
        return asyncMqttBrokerUrl;
    }

    /**
     * Default MQTT broker URL for async messaging. Used when a
     * {@code PUT /mockserver/asyncapi} request omits {@code brokerConfig.mqttBrokerUrl}.
     *
     * @param asyncMqttBrokerUrl the default MQTT broker URL
     */
    public Configuration asyncMqttBrokerUrl(String asyncMqttBrokerUrl) {
        this.asyncMqttBrokerUrl = asyncMqttBrokerUrl;
        return this;
    }

    public Integer asyncRecordedMessageMaxEntries() {
        if (asyncRecordedMessageMaxEntries == null) {
            return ConfigurationProperties.asyncRecordedMessageMaxEntries();
        }
        return asyncRecordedMessageMaxEntries;
    }

    /**
     * Maximum number of recorded messages retained per channel in async
     * messaging subscribers. Default is 1000.
     *
     * @param asyncRecordedMessageMaxEntries the maximum entries per channel
     */
    public Configuration asyncRecordedMessageMaxEntries(Integer asyncRecordedMessageMaxEntries) {
        this.asyncRecordedMessageMaxEntries = asyncRecordedMessageMaxEntries;
        return this;
    }

    public void addSubjectAlternativeName(String host) {
        if (isNotBlank(host)) {
            String hostWithoutPort = substringBefore(host, ":");
            if (isNotBlank(hostWithoutPort)) {
                if (InetAddresses.isInetAddress(hostWithoutPort)) {
                    addSslSubjectAlternativeNameIps(hostWithoutPort);
                } else {
                    addSslSubjectAlternativeNameDomains(hostWithoutPort);
                }
            }
        }
    }

    public void addSslSubjectAlternativeNameIps(String... additionalSubjectAlternativeNameIps) {
        boolean subjectAlternativeIpsModified = false;
        Set<String> sslSubjectAlternativeNameIps = sslSubjectAlternativeNameIps();
        for (String subjectAlternativeIp : additionalSubjectAlternativeNameIps) {
            if (sslSubjectAlternativeNameIps.add(subjectAlternativeIp.trim())) {
                subjectAlternativeIpsModified = true;
            }
        }
        if (subjectAlternativeIpsModified) {
            rebuildServerTLSContext(true);
            sslSubjectAlternativeNameIps(sslSubjectAlternativeNameIps);
        }
    }

    public void clearSslSubjectAlternativeNameIps() {
        sslSubjectAlternativeNameIps.clear();
        rebuildServerTLSContext(true);
    }

    public void addSslSubjectAlternativeNameDomains(String... additionalSubjectAlternativeNameDomains) {
        boolean subjectAlternativeDomainsModified = false;
        Set<String> sslSubjectAlternativeNameDomains = sslSubjectAlternativeNameDomains();
        for (String subjectAlternativeDomain : additionalSubjectAlternativeNameDomains) {
            if (sslSubjectAlternativeNameDomains.add(subjectAlternativeDomain.trim())) {
                subjectAlternativeDomainsModified = true;
            }
        }
        if (subjectAlternativeDomainsModified) {
            rebuildServerTLSContext(true);
            sslSubjectAlternativeNameDomains(sslSubjectAlternativeNameDomains);
        }
    }

    public void clearSslSubjectAlternativeNameDomains() {
        sslSubjectAlternativeNameDomains.clear();
        rebuildServerTLSContext(true);
    }

    public int ringBufferSize() {
        return nextPowerOfTwo(maxLogEntries());
    }

    private int nextPowerOfTwo(int value) {
        for (int i = 0; i < 30; i++) {
            int powOfTwo = 1 << i;
            if (powOfTwo > value) {
                return powOfTwo;
            }
        }
        return 1 << 30;
    }

}
