001/*
002 * Copyright © 2025 CUI-OpenSource-Software (info@cuioss.de)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.benchmarking.common.base;
017
018import de.cuioss.benchmarking.common.http.HttpClientFactory;
019import de.cuioss.tools.logging.CuiLogger;
020
021import java.io.IOException;
022import java.net.URI;
023import java.net.http.HttpClient;
024import java.net.http.HttpRequest;
025import java.net.http.HttpResponse;
026import java.time.Duration;
027
028/**
029 * Abstract base class for all benchmarks in the OAuth Sheriff project.
030 * Provides common functionality and fields to reduce code duplication
031 * across different benchmark implementations.
032 *
033 * <p>This class provides:
034 * <ul>
035 *   <li>Logging infrastructure</li>
036 *   <li>HTTP client management</li>
037 *   <li>Common configuration fields</li>
038 *   <li>Utility methods for HTTP operations</li>
039 *   <li>Metrics export hooks</li>
040 * </ul>
041 *
042 * @author Oliver Wolff
043 */
044public abstract class AbstractBenchmarkBase {
045
046    static {
047        // Set the logging manager to prevent JBoss LogManager errors in forked JVMs
048        System.setProperty("java.util.logging.manager", "java.util.logging.LogManager");
049    }
050
051    private static final CuiLogger LOGGER = new CuiLogger(AbstractBenchmarkBase.class);
052
053    protected String serviceUrl;
054    protected String benchmarkResultsDir;
055    protected HttpClient httpClient;
056
057
058    /**
059     * Base setup method that initializes common resources.
060     * Subclasses should call this from their @Setup method.
061     *
062     * <p>This method:
063     * <ul>
064     *   <li>Initializes the benchmark results directory</li>
065     *   <li>Performs additional setup via template method</li>
066     *   <li>Creates the HTTP client after serviceUrl is set</li>
067     *   <li>Performs post-initialization setup after HTTP client is ready</li>
068     * </ul>
069     */
070    protected void setupBase() {
071        // Initialize benchmark results directory
072        benchmarkResultsDir = System.getProperty("benchmark.results.dir", "target/benchmark-results");
073
074        // Call template method for subclass-specific setup (sets serviceUrl)
075        performAdditionalSetup();
076
077        // Initialize HttpClient AFTER serviceUrl has been set
078        initializeHttpClient();
079
080        LOGGER.debug("Base benchmark setup completed");
081    }
082
083    /**
084     * Initialize the HTTP client. This is called after performAdditionalSetup()
085     * to allow serviceUrl to be set first for URL-based client caching.
086     * Subclasses can override this to customize HTTP client initialization.
087     */
088    protected void initializeHttpClient() {
089        httpClient = HttpClientFactory.getInsecureClient();
090        LOGGER.debug("Using shared Java HttpClient");
091    }
092
093    /**
094     * Template method for subclasses to perform additional setup.
095     * Override this method to add specific initialization logic.
096     */
097    protected abstract void performAdditionalSetup();
098
099    /**
100     * Teardown method called after benchmark execution.
101     * Subclasses should call this from their @TearDown method
102     * or override to add additional cleanup logic.
103     */
104    protected void tearDown() {
105        LOGGER.debug("Benchmark teardown initiated");
106
107        // Call template method for subclass-specific teardown
108        performAdditionalTeardown();
109
110        LOGGER.debug("Benchmark teardown completed");
111    }
112
113    /**
114     * Template method for subclasses to perform additional teardown.
115     * Override this method to add specific cleanup logic.
116     */
117    protected void performAdditionalTeardown() {
118        // Default implementation does nothing
119    }
120
121    /**
122     * Creates a basic HTTP request builder with common headers.
123     *
124     * @param url the full URL to send the request to
125     * @return configured request builder
126     */
127    protected HttpRequest.Builder createBaseRequest(String url) {
128        return HttpRequest.newBuilder()
129                .uri(URI.create(url))
130                .header("Content-Type", "application/json")
131                .header("Accept", "application/json")
132                .timeout(Duration.ofSeconds(30));
133    }
134
135    /**
136     * Creates a basic HTTP request builder with common headers using a base URL and path.
137     *
138     * @param baseUrl the base URL
139     * @param path the path to append to the base URL
140     * @return configured request builder
141     */
142    protected HttpRequest.Builder createBaseRequest(String baseUrl, String path) {
143        return createBaseRequest(baseUrl + path);
144    }
145
146    /**
147     * Sends an HTTP request and returns the response.
148     *
149     * @param request the request to send
150     * @return the HTTP response
151     * @throws IOException if an I/O error occurs
152     * @throws InterruptedException if the operation is interrupted
153     */
154    protected HttpResponse<String> sendRequest(HttpRequest request) throws IOException, InterruptedException {
155        if (httpClient == null) {
156            throw new IllegalStateException("HTTP client not initialized. Ensure setupBase() was called.");
157        }
158
159        // Send request using Java HttpClient
160        return httpClient.send(request, HttpResponse.BodyHandlers.ofString());
161    }
162
163    /**
164     * Utility method to handle common error scenarios in benchmarks.
165     *
166     * @param response the response to check
167     * @param expectedStatus the expected HTTP status code
168     * @throws IllegalStateException if the response status doesn't match expected
169     */
170    protected void validateResponse(HttpResponse<String> response, int expectedStatus) {
171        if (response.statusCode() != expectedStatus) {
172            throw new IllegalStateException("Expected status %d but got %d. Response: %s".formatted(
173                    expectedStatus, response.statusCode(), response.body()));
174        }
175    }
176
177    /**
178     * Export metrics at the end of benchmark execution.
179     * Subclasses should override this method to implement specific metrics export logic.
180     */
181    public abstract void exportBenchmarkMetrics();
182}