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.repository;
017
018import lombok.Builder;
019import lombok.Value;
020
021/**
022 * Configuration for TokenRepository to connect to Keycloak and fetch tokens
023 * for benchmark testing.
024 *
025 */
026@Value
027@Builder
028public class TokenRepositoryConfig {
029
030    /**
031     * System property keys for token repository configuration.
032     */
033    public static final class Properties {
034        public static final String KEYCLOAK_URL = "token.keycloak.url";
035        public static final String REALM = "token.keycloak.realm";
036        public static final String CLIENT_ID = "token.keycloak.clientId";
037        public static final String CLIENT_SECRET = "token.keycloak.clientSecret";
038        public static final String USERNAME = "token.keycloak.username";
039        public static final String PASSWORD = "token.keycloak.password";
040        public static final String POOL_SIZE = "token.pool.size";
041        public static final String CONNECTION_TIMEOUT_MS = "token.connection.timeoutMs";
042        public static final String REQUEST_TIMEOUT_MS = "token.request.timeoutMs";
043        public static final String VERIFY_SSL = "token.verifySsl";
044        public static final String REFRESH_THRESHOLD_SECONDS = "token.refreshThresholdSeconds";
045
046        private Properties() {
047        }
048    }
049
050    /**
051     * The base URL of the Keycloak server.
052     * Example: https://localhost:1443
053     * Required - must be provided via properties or builder.
054     */
055    String keycloakBaseUrl;
056
057    /**
058     * The Keycloak realm name.
059     * Required - must be provided via properties or builder.
060     */
061    String realm;
062
063    /**
064     * The client ID for token requests.
065     * Required - must be provided via properties or builder.
066     */
067    String clientId;
068
069    /**
070     * The client secret for token requests.
071     * Required - must be provided via properties or builder.
072     */
073    String clientSecret;
074
075    /**
076     * The username for token requests.
077     * Required - must be provided via properties or builder.
078     */
079    String username;
080
081    /**
082     * The password for token requests.
083     * Required - must be provided via properties or builder.
084     */
085    String password;
086
087    /**
088     * Number of tokens to fetch and cache for rotation.
089     * This should be configured to achieve approximately 10% cache hit ratio
090     * based on the expected number of benchmark requests.
091     * Default: 5000 (10x the default cache size of 500)
092     */
093    @Builder.Default
094    int tokenPoolSize = 100;
095
096    /**
097     * Connection timeout in milliseconds for Keycloak requests.
098     * Default: 5000ms (5 seconds)
099     */
100    @Builder.Default
101    int connectionTimeoutMs = 5000;
102
103    /**
104     * Request timeout in milliseconds for Keycloak requests.
105     * Default: 10000ms (10 seconds)
106     */
107    @Builder.Default
108    int requestTimeoutMs = 10000;
109
110    /**
111     * Whether to verify SSL certificates when connecting to Keycloak.
112     * Should be false for local testing with self-signed certificates.
113     * Default: false
114     */
115    @Builder.Default
116    boolean verifySsl = false;
117
118    /**
119     * Token refresh threshold - tokens will be refreshed when they have less
120     * than this many seconds left before expiration.
121     * Default: 180 seconds (3 minutes) - safe margin for 15-minute tokens
122     */
123    @Builder.Default
124    int tokenRefreshThresholdSeconds = 180;
125
126    /**
127     * Creates a configuration from system properties.
128     * All required properties must be provided via system properties or Maven -D arguments.
129     * 
130     * @return TokenRepositoryConfig with values from properties
131     * @throws IllegalArgumentException if any required property is missing
132     */
133    public static TokenRepositoryConfig fromProperties() {
134        return TokenRepositoryConfig.builder()
135                .keycloakBaseUrl(requireProperty(System.getProperty(Properties.KEYCLOAK_URL), "Keycloak URL", Properties.KEYCLOAK_URL))
136                .realm(requireProperty(System.getProperty(Properties.REALM), "Keycloak realm", Properties.REALM))
137                .clientId(requireProperty(System.getProperty(Properties.CLIENT_ID), "Client ID", Properties.CLIENT_ID))
138                .clientSecret(requireProperty(System.getProperty(Properties.CLIENT_SECRET), "Client secret", Properties.CLIENT_SECRET))
139                .username(requireProperty(System.getProperty(Properties.USERNAME), "Username", Properties.USERNAME))
140                .password(requireProperty(System.getProperty(Properties.PASSWORD), "Password", Properties.PASSWORD))
141                .tokenPoolSize(Integer.parseInt(System.getProperty(Properties.POOL_SIZE, "100")))
142                .connectionTimeoutMs(Integer.parseInt(System.getProperty(Properties.CONNECTION_TIMEOUT_MS, "5000")))
143                .requestTimeoutMs(Integer.parseInt(System.getProperty(Properties.REQUEST_TIMEOUT_MS, "10000")))
144                .verifySsl(Boolean.parseBoolean(System.getProperty(Properties.VERIFY_SSL, "false")))
145                .tokenRefreshThresholdSeconds(Integer.parseInt(System.getProperty(Properties.REFRESH_THRESHOLD_SECONDS, "180")))
146                .build();
147    }
148
149    /**
150     * Validates that a system property is not null or empty.
151     * 
152     * @param value the property value to check
153     * @param description human-readable description of what the property represents
154     * @param propertyName the system property name
155     * @return the validated value
156     * @throws IllegalArgumentException if the value is null or empty
157     */
158    public static String requireProperty(String value, String description, String propertyName) {
159        if (value == null || value.trim().isEmpty()) {
160            throw new IllegalArgumentException("%s is required but not provided. Set system property: %s".formatted(
161                    description, propertyName));
162        }
163        return value;
164    }
165}