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}