2323import org .thingsboard .client .model .LoginResponse ;
2424
2525import java .io .InputStream ;
26- import java .net .Authenticator ;
27- import java .net .CookieHandler ;
28- import java .net .ProxySelector ;
2926import java .net .URI ;
3027import java .net .http .HttpClient ;
3128import java .net .http .HttpRequest ;
3229import java .net .http .HttpResponse ;
33- import java .time .Duration ;
3430import java .util .Base64 ;
3531import java .util .Map ;
36- import java .util .concurrent .Executor ;
3732import java .util .concurrent .TimeUnit ;
3833import java .util .logging .Level ;
39- import javax .net .ssl .SSLContext ;
40- import javax .net .ssl .SSLParameters ;
4134
4235/**
4336 * High-level ThingsBoard REST API client with automatic authentication management.
7366 * .maxRetryDelayMs(60_000L)
7467 * .build();
7568 *
76- * // Disable retry entirely
69+ * // Disable rate-limit retry
7770 * ThingsboardClient client = ThingsboardClient.builder()
7871 * .url("http://localhost:8080")
7972 * .credentials("tenant@thingsboard.org", "password")
80- * .retryEnabled (false)
73+ * .retryOnRateLimit (false)
8174 * .build();
8275 *
8376 * // All generated API methods are available directly
@@ -120,7 +113,7 @@ public static class Builder {
120113 private String username ;
121114 private String password ;
122115 private String apiKey ;
123- private boolean retryEnabled = true ;
116+ private boolean retryOnRateLimit = true ;
124117 private int maxRetries = 3 ;
125118 private long initialRetryDelayMs = 1000L ;
126119 private long maxRetryDelayMs = 30_000L ;
@@ -144,11 +137,11 @@ public Builder apiKey(String apiKey) {
144137 }
145138
146139 /**
147- * Enables or disables automatic retry on HTTP 429 responses.
148- * Retry is enabled by default.
140+ * Enables or disables automatic retry on rate-limit ( HTTP 429) responses.
141+ * Enabled by default.
149142 */
150- public Builder retryEnabled (boolean retryEnabled ) {
151- this .retryEnabled = retryEnabled ;
143+ public Builder retryOnRateLimit (boolean retryOnRateLimit ) {
144+ this .retryOnRateLimit = retryOnRateLimit ;
152145 return this ;
153146 }
154147
@@ -184,101 +177,39 @@ public ThingsboardClient build() throws ApiException {
184177 if (url == null ) {
185178 throw new IllegalArgumentException ("url is required" );
186179 }
187- if (apiKey != null ) {
188- AuthManager auth = new AuthManager (url , AuthType .API_KEY , apiKey );
189- installRetryingClient (auth );
190- return new ThingsboardClient (auth );
191- }
192- AuthManager auth = new AuthManager (url , AuthType .JWT , null );
193- installRetryingClient (auth );
180+ ApiClient apiClient = retryOnRateLimit
181+ ? new RetryableApiClient (maxRetries , initialRetryDelayMs , maxRetryDelayMs )
182+ : new ApiClient ();
183+ AuthType authType = apiKey != null ? AuthType .API_KEY : AuthType .JWT ;
184+ AuthManager auth = new AuthManager (url , authType , apiKey , apiClient );
194185 ThingsboardClient client = new ThingsboardClient (auth );
195- if (username != null ) {
186+ if (authType == AuthType . JWT && username != null ) {
196187 client .login (username , password );
197188 }
198189 return client ;
199190 }
200191
201- private void installRetryingClient (AuthManager auth ) {
202- if (!retryEnabled ) {
203- return ;
204- }
205- // Replace the HttpClient already captured by AuthManager for raw auth calls
206- RetryingHttpClient retrying = RetryingHttpClient .wrap (
207- auth .httpClient , maxRetries , initialRetryDelayMs , maxRetryDelayMs );
208- auth .httpClient = retrying ;
209-
210- // Replace the ApiClient's builder so that ThingsboardApi (constructed next)
211- // also gets a RetryingHttpClient when it calls apiClient.getHttpClient()
212- HttpClient .Builder realBuilder = auth .apiClient .builder ;
213- auth .apiClient .builder = new HttpClient .Builder () {
214- @ Override
215- public HttpClient build () {
216- return RetryingHttpClient .wrap (
217- realBuilder .build (), maxRetries , initialRetryDelayMs , maxRetryDelayMs );
218- }
219-
220- @ Override
221- public HttpClient .Builder cookieHandler (CookieHandler cookieHandler ) {
222- realBuilder .cookieHandler (cookieHandler );
223- return this ;
224- }
225-
226- @ Override
227- public HttpClient .Builder connectTimeout (Duration duration ) {
228- realBuilder .connectTimeout (duration );
229- return this ;
230- }
231-
232- @ Override
233- public HttpClient .Builder sslContext (SSLContext sslContext ) {
234- realBuilder .sslContext (sslContext );
235- return this ;
236- }
237-
238- @ Override
239- public HttpClient .Builder sslParameters (SSLParameters sslParameters ) {
240- realBuilder .sslParameters (sslParameters );
241- return this ;
242- }
243-
244- @ Override
245- public HttpClient .Builder executor (Executor executor ) {
246- realBuilder .executor (executor );
247- return this ;
248- }
249-
250- @ Override
251- public HttpClient .Builder followRedirects (HttpClient .Redirect policy ) {
252- realBuilder .followRedirects (policy );
253- return this ;
254- }
255-
256- @ Override
257- public HttpClient .Builder version (HttpClient .Version version ) {
258- realBuilder .version (version );
259- return this ;
260- }
192+ }
261193
262- @ Override
263- public HttpClient .Builder proxy (ProxySelector proxySelector ) {
264- realBuilder .proxy (proxySelector );
265- return this ;
266- }
194+ /**
195+ * ApiClient subclass that wraps every built HttpClient with retry-on-429 logic.
196+ */
197+ private static class RetryableApiClient extends ApiClient {
267198
268- @ Override
269- public HttpClient .Builder authenticator (java .net .Authenticator authenticator ) {
270- realBuilder .authenticator (authenticator );
271- return this ;
272- }
199+ private final int maxRetries ;
200+ private final long initialDelayMs ;
201+ private final long maxDelayMs ;
273202
274- @ Override
275- public HttpClient .Builder priority (int priority ) {
276- realBuilder .priority (priority );
277- return this ;
278- }
279- };
203+ RetryableApiClient (int maxRetries , long initialDelayMs , long maxDelayMs ) {
204+ this .maxRetries = maxRetries ;
205+ this .initialDelayMs = initialDelayMs ;
206+ this .maxDelayMs = maxDelayMs ;
280207 }
281208
209+ @ Override
210+ public HttpClient getHttpClient () {
211+ return RetryingHttpClient .wrap (super .getHttpClient (), maxRetries , initialDelayMs , maxDelayMs );
212+ }
282213 }
283214
284215 /**
@@ -318,7 +249,7 @@ private record TokenInfo(String token, String refreshToken, long tokenExpTs,
318249
319250 final ApiClient apiClient ;
320251 private final AuthType authType ;
321- HttpClient httpClient ; // package- private and non- final so Builder can replace with RetryingHttpClient
252+ private final HttpClient httpClient ;
322253 private final ObjectMapper objectMapper ;
323254 private final String baseUrl ;
324255
@@ -327,9 +258,9 @@ private record TokenInfo(String token, String refreshToken, long tokenExpTs,
327258 private volatile String password ;
328259 private volatile boolean refreshing ;
329260
330- AuthManager (String url , AuthType authType , String apiKey ) {
261+ AuthManager (String url , AuthType authType , String apiKey , ApiClient apiClient ) {
331262 this .authType = authType ;
332- this .apiClient = new ApiClient () ;
263+ this .apiClient = apiClient ;
333264 apiClient .updateBaseUri (url );
334265 this .baseUrl = apiClient .getBaseUri ();
335266 this .httpClient = apiClient .getHttpClient ();
0 commit comments