001/* 002 * Copyright (C) 2022-present The Prometheus jmx_exporter Authors 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 */ 016 017package io.prometheus.jmx.common.http; 018 019import com.sun.net.httpserver.Authenticator; 020import com.sun.net.httpserver.HttpsConfigurator; 021import io.prometheus.jmx.common.configuration.ConvertToInteger; 022import io.prometheus.jmx.common.configuration.ConvertToMapAccessor; 023import io.prometheus.jmx.common.configuration.ConvertToString; 024import io.prometheus.jmx.common.configuration.ValidateIntegerInRange; 025import io.prometheus.jmx.common.configuration.ValidateStringIsNotBlank; 026import io.prometheus.jmx.common.http.authenticator.MessageDigestAuthenticator; 027import io.prometheus.jmx.common.http.authenticator.PBKDF2Authenticator; 028import io.prometheus.jmx.common.http.authenticator.PlaintextAuthenticator; 029import io.prometheus.jmx.common.http.ssl.SSLContextFactory; 030import io.prometheus.jmx.common.yaml.YamlMapAccessor; 031import io.prometheus.metrics.exporter.httpserver.HTTPServer; 032import io.prometheus.metrics.model.registry.PrometheusRegistry; 033import java.io.File; 034import java.io.FileReader; 035import java.io.IOException; 036import java.io.Reader; 037import java.net.InetAddress; 038import java.security.GeneralSecurityException; 039import java.util.HashMap; 040import java.util.HashSet; 041import java.util.Map; 042import java.util.Optional; 043import java.util.Set; 044import java.util.concurrent.Executors; 045import java.util.concurrent.RejectedExecutionHandler; 046import java.util.concurrent.SynchronousQueue; 047import java.util.concurrent.ThreadFactory; 048import java.util.concurrent.ThreadPoolExecutor; 049import java.util.concurrent.TimeUnit; 050import java.util.concurrent.atomic.AtomicInteger; 051import org.yaml.snakeyaml.Yaml; 052 053/** 054 * Class to create the HTTPServer used by both the Java agent exporter and the standalone exporter 055 */ 056public class HTTPServerFactory { 057 058 private static final int DEFAULT_MINIMUM_THREADS = 1; 059 private static final int DEFAULT_MAXIMUM_THREADS = 10; 060 private static final int DEFAULT_KEEP_ALIVE_TIME_SECONDS = 120; 061 062 private static final String REALM = "/"; 063 private static final String PLAINTEXT = "plaintext"; 064 private static final Set<String> SHA_ALGORITHMS; 065 private static final Set<String> PBKDF2_ALGORITHMS; 066 private static final Map<String, Integer> PBKDF2_ALGORITHM_ITERATIONS; 067 private static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore"; 068 private static final String JAVAX_NET_SSL_KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword"; 069 070 private static final int PBKDF2_KEY_LENGTH_BITS = 128; 071 072 static { 073 SHA_ALGORITHMS = new HashSet<>(); 074 SHA_ALGORITHMS.add("SHA-1"); 075 SHA_ALGORITHMS.add("SHA-256"); 076 SHA_ALGORITHMS.add("SHA-512"); 077 078 PBKDF2_ALGORITHMS = new HashSet<>(); 079 PBKDF2_ALGORITHMS.add("PBKDF2WithHmacSHA1"); 080 PBKDF2_ALGORITHMS.add("PBKDF2WithHmacSHA256"); 081 PBKDF2_ALGORITHMS.add("PBKDF2WithHmacSHA512"); 082 083 PBKDF2_ALGORITHM_ITERATIONS = new HashMap<>(); 084 PBKDF2_ALGORITHM_ITERATIONS.put("PBKDF2WithHmacSHA1", 1300000); 085 PBKDF2_ALGORITHM_ITERATIONS.put("PBKDF2WithHmacSHA256", 600000); 086 PBKDF2_ALGORITHM_ITERATIONS.put("PBKDF2WithHmacSHA512", 210000); 087 } 088 089 private YamlMapAccessor rootYamlMapAccessor; 090 091 /** Constructor */ 092 public HTTPServerFactory() { 093 // DO NOTHING 094 } 095 096 /** 097 * Method to create an HTTPServer using the supplied arguments 098 * 099 * @param inetAddress inetAddress 100 * @param port port 101 * @param prometheusRegistry prometheusRegistry 102 * @param exporterYamlFile exporterYamlFile 103 * @return an HTTPServer 104 * @throws IOException IOException 105 */ 106 public HTTPServer createHTTPServer( 107 InetAddress inetAddress, 108 int port, 109 PrometheusRegistry prometheusRegistry, 110 File exporterYamlFile) 111 throws IOException { 112 HTTPServer.Builder httpServerBuilder = 113 HTTPServer.builder() 114 .inetAddress(inetAddress) 115 .port(port) 116 .registry(prometheusRegistry); 117 118 createMapAccessor(exporterYamlFile); 119 configureThreads(httpServerBuilder); 120 configureAuthentication(httpServerBuilder); 121 configureSSL(httpServerBuilder); 122 123 return httpServerBuilder.buildAndStart(); 124 } 125 126 /** 127 * Method to create a MapAccessor for accessing YAML configuration 128 * 129 * @param exporterYamlFile exporterYamlFile 130 */ 131 private void createMapAccessor(File exporterYamlFile) { 132 try (Reader reader = new FileReader(exporterYamlFile)) { 133 Map<Object, Object> yamlMap = new Yaml().load(reader); 134 rootYamlMapAccessor = new YamlMapAccessor(yamlMap); 135 } catch (Throwable t) { 136 throw new ConfigurationException( 137 String.format("Exception loading exporter YAML file [%s]", exporterYamlFile), 138 t); 139 } 140 } 141 142 /** 143 * Method to configure the HTTPServer thread pool 144 * 145 * @param httpServerBuilder httpServerBuilder 146 */ 147 private void configureThreads(HTTPServer.Builder httpServerBuilder) { 148 int minimum = DEFAULT_MINIMUM_THREADS; 149 int maximum = DEFAULT_MAXIMUM_THREADS; 150 int keepAliveTime = DEFAULT_KEEP_ALIVE_TIME_SECONDS; 151 152 if (rootYamlMapAccessor.containsPath("/httpServer/threads")) { 153 YamlMapAccessor httpServerThreadsMapAccessor = 154 rootYamlMapAccessor 155 .get("/httpServer/threads") 156 .map( 157 new ConvertToMapAccessor( 158 ConfigurationException.supplier( 159 "Invalid configuration for" 160 + " /httpServer/threads"))) 161 .orElseThrow( 162 ConfigurationException.supplier( 163 "/httpServer/threads configuration values are" 164 + " required")); 165 166 minimum = 167 httpServerThreadsMapAccessor 168 .get("/minimum") 169 .map( 170 new ConvertToInteger( 171 ConfigurationException.supplier( 172 "Invalid configuration for" 173 + " /httpServer/threads/minimum must be an" 174 + " integer"))) 175 .map( 176 new ValidateIntegerInRange( 177 0, 178 Integer.MAX_VALUE, 179 ConfigurationException.supplier( 180 "Invalid configuration for" 181 + " /httpServer/threads/minimum must be 0" 182 + " or greater"))) 183 .orElseThrow( 184 ConfigurationException.supplier( 185 "/httpServer/threads/minimum is a required integer")); 186 187 maximum = 188 httpServerThreadsMapAccessor 189 .get("/maximum") 190 .map( 191 new ConvertToInteger( 192 ConfigurationException.supplier( 193 "Invalid configuration for" 194 + " /httpServer/threads/maximum must be an" 195 + " integer"))) 196 .map( 197 new ValidateIntegerInRange( 198 1, 199 Integer.MAX_VALUE, 200 ConfigurationException.supplier( 201 "Invalid configuration for" 202 + " /httpServer/threads/maxPoolSize must be" 203 + " between greater than 0"))) 204 .orElseThrow( 205 ConfigurationException.supplier( 206 "/httpServer/threads/maximum is a required integer")); 207 208 keepAliveTime = 209 httpServerThreadsMapAccessor 210 .get("/keepAliveTime") 211 .map( 212 new ConvertToInteger( 213 ConfigurationException.supplier( 214 "Invalid configuration for" 215 + " /httpServer/threads/keepAliveTime must" 216 + " be an integer"))) 217 .map( 218 new ValidateIntegerInRange( 219 1, 220 Integer.MAX_VALUE, 221 ConfigurationException.supplier( 222 "Invalid configuration for" 223 + " /httpServer/threads/keepAliveTime must" 224 + " be greater than 0"))) 225 .orElseThrow( 226 ConfigurationException.supplier( 227 "/httpServer/threads/keepAliveTime is a required" 228 + " integer")); 229 230 if (maximum < minimum) { 231 throw new ConfigurationException( 232 "/httpServer/threads/maximum must be greater than or equal to" 233 + " /httpServer/threads/minimum"); 234 } 235 } 236 237 ThreadPoolExecutor threadPoolExecutor = 238 new ThreadPoolExecutor( 239 minimum, 240 maximum, 241 keepAliveTime, 242 TimeUnit.SECONDS, 243 new SynchronousQueue<>(true), 244 NamedDaemonThreadFactory.defaultThreadFactory(true), 245 new BlockingRejectedExecutionHandler()); 246 247 httpServerBuilder.executorService(threadPoolExecutor); 248 } 249 250 /** 251 * Method to configure authentication 252 * 253 * @param httpServerBuilder httpServerBuilder 254 */ 255 private void configureAuthentication(HTTPServer.Builder httpServerBuilder) { 256 Authenticator authenticator; 257 258 if (rootYamlMapAccessor.containsPath("/httpServer/authentication")) { 259 Optional<Object> authenticatorClassAttribute = 260 rootYamlMapAccessor.get("/httpServer/authentication/plugin"); 261 if (authenticatorClassAttribute.isPresent()) { 262 263 YamlMapAccessor httpServerAuthenticationCustomAuthenticatorYamlMapAccessor = 264 rootYamlMapAccessor 265 .get("/httpServer/authentication/plugin") 266 .map( 267 new ConvertToMapAccessor( 268 ConfigurationException.supplier( 269 "Invalid configuration for" 270 + " /httpServer/authentication/plugin"))) 271 .orElseThrow( 272 ConfigurationException.supplier( 273 "/httpServer/authentication/plugin" 274 + " configuration values are required")); 275 276 String authenticatorClass = 277 httpServerAuthenticationCustomAuthenticatorYamlMapAccessor 278 .get("/class") 279 .map( 280 new ConvertToString( 281 ConfigurationException.supplier( 282 "Invalid configuration for" 283 + " /httpServer/authentication/plugin/class" 284 + " must be a string"))) 285 .map( 286 new ValidateStringIsNotBlank( 287 ConfigurationException.supplier( 288 "Invalid configuration for" 289 + " /httpServer/authentication/plugin/class" 290 + " must not be blank"))) 291 .orElseThrow( 292 ConfigurationException.supplier( 293 "/httpServer/authentication/plugin/class is a" 294 + " required string")); 295 296 Optional<Object> subjectAttribute = 297 httpServerAuthenticationCustomAuthenticatorYamlMapAccessor.get( 298 "/subjectAttributeName"); 299 if (subjectAttribute.isPresent()) { 300 String subjectAttributeName = 301 subjectAttribute 302 .map( 303 new ConvertToString( 304 ConfigurationException.supplier( 305 "Invalid configuration for" 306 + " /httpServer/authentication/plugin/class/subjectAttributeName" 307 + " must be a string"))) 308 .map( 309 new ValidateStringIsNotBlank( 310 ConfigurationException.supplier( 311 "Invalid configuration for" 312 + " /httpServer/authentication/plugin/subjectAttributeName" 313 + " must not be blank"))) 314 .get(); 315 316 // need subject.doAs for subsequent handlers 317 httpServerBuilder.authenticatedSubjectAttributeName(subjectAttributeName); 318 } 319 320 authenticator = loadAuthenticator(authenticatorClass); 321 } else { 322 YamlMapAccessor httpServerAuthenticationBasicYamlMapAccessor = 323 rootYamlMapAccessor 324 .get("/httpServer/authentication/basic") 325 .map( 326 new ConvertToMapAccessor( 327 ConfigurationException.supplier( 328 "Invalid configuration for" 329 + " /httpServer/authentication/basic"))) 330 .orElseThrow( 331 ConfigurationException.supplier( 332 "/httpServer/authentication/basic configuration" 333 + " values are required")); 334 335 String username = 336 httpServerAuthenticationBasicYamlMapAccessor 337 .get("/username") 338 .map( 339 new ConvertToString( 340 ConfigurationException.supplier( 341 "Invalid configuration for" 342 + " /httpServer/authentication/basic/username" 343 + " must be a string"))) 344 .map( 345 new ValidateStringIsNotBlank( 346 ConfigurationException.supplier( 347 "Invalid configuration for" 348 + " /httpServer/authentication/basic/username" 349 + " must not be blank"))) 350 .orElseThrow( 351 ConfigurationException.supplier( 352 "/httpServer/authentication/basic/username is a" 353 + " required string")); 354 355 String algorithm = 356 httpServerAuthenticationBasicYamlMapAccessor 357 .get("/algorithm") 358 .map( 359 new ConvertToString( 360 ConfigurationException.supplier( 361 "Invalid configuration for" 362 + " /httpServer/authentication/basic/algorithm" 363 + " must be a string"))) 364 .map( 365 new ValidateStringIsNotBlank( 366 ConfigurationException.supplier( 367 "Invalid configuration for" 368 + " /httpServer/authentication/basic/algorithm" 369 + " must not be blank"))) 370 .orElse(PLAINTEXT); 371 372 if (PLAINTEXT.equalsIgnoreCase(algorithm)) { 373 String password = 374 httpServerAuthenticationBasicYamlMapAccessor 375 .get("/password") 376 .map( 377 new ConvertToString( 378 ConfigurationException.supplier( 379 "Invalid configuration for" 380 + " /httpServer/authentication/basic/password" 381 + " must be a string"))) 382 .map( 383 new ValidateStringIsNotBlank( 384 ConfigurationException.supplier( 385 "Invalid configuration for" 386 + " /httpServer/authentication/basic/password" 387 + " must not be blank"))) 388 .orElseThrow( 389 ConfigurationException.supplier( 390 "/httpServer/authentication/basic/password is a" 391 + " required string")); 392 393 authenticator = new PlaintextAuthenticator("/", username, password); 394 } else if (SHA_ALGORITHMS.contains(algorithm) 395 || PBKDF2_ALGORITHMS.contains(algorithm)) { 396 String hash = 397 httpServerAuthenticationBasicYamlMapAccessor 398 .get("/passwordHash") 399 .map( 400 new ConvertToString( 401 ConfigurationException.supplier( 402 "Invalid configuration for" 403 + " /httpServer/authentication/basic/passwordHash" 404 + " must be a string"))) 405 .map( 406 new ValidateStringIsNotBlank( 407 ConfigurationException.supplier( 408 "Invalid configuration for" 409 + " /httpServer/authentication/basic/passwordHash" 410 + " must not be blank"))) 411 .orElseThrow( 412 ConfigurationException.supplier( 413 "/httpServer/authentication/basic/passwordHash" 414 + " is a required string")); 415 416 if (SHA_ALGORITHMS.contains(algorithm)) { 417 authenticator = 418 createMessageDigestAuthenticator( 419 httpServerAuthenticationBasicYamlMapAccessor, 420 REALM, 421 username, 422 hash, 423 algorithm); 424 } else { 425 authenticator = 426 createPBKDF2Authenticator( 427 httpServerAuthenticationBasicYamlMapAccessor, 428 REALM, 429 username, 430 hash, 431 algorithm); 432 } 433 } else { 434 throw new ConfigurationException( 435 String.format( 436 "Unsupported /httpServer/authentication/basic/algorithm [%s]", 437 algorithm)); 438 } 439 } 440 441 httpServerBuilder.authenticator(authenticator); 442 } 443 } 444 445 private Authenticator loadAuthenticator(String className) { 446 447 Class<?> clazz; 448 try { 449 clazz = this.getClass().getClassLoader().loadClass(className); 450 } catch (ClassNotFoundException e) { 451 throw new ConfigurationException( 452 String.format( 453 "configured /httpServer/authentication/authenticatorClass [%s]" 454 + " not found, loadClass resulted in [%s:%s]", 455 className, e.getClass(), e.getMessage())); 456 } 457 if (!Authenticator.class.isAssignableFrom(clazz)) { 458 throw new ConfigurationException( 459 String.format( 460 "configured /httpServer/authentication/authenticatorClass [%s]" 461 + " loadClass resulted in [%s] of the wrong type, is not assignable" 462 + " from Authenticator", 463 className, clazz.getCanonicalName())); 464 } 465 try { 466 return (Authenticator) clazz.getDeclaredConstructor().newInstance(); 467 } catch (Exception e) { 468 throw new ConfigurationException( 469 String.format( 470 "configured /httpServer/authentication/authenticatorClass [%s] no arg" 471 + " constructor newInstance resulted in exception [%s:%s]", 472 className, e.getClass(), e.getMessage())); 473 } 474 } 475 476 /** 477 * Method to create a MessageDigestAuthenticator 478 * 479 * @param httpServerAuthenticationBasicYamlMapAccessor httpServerAuthenticationBasicMapAccessor 480 * @param realm realm 481 * @param username username 482 * @param password password 483 * @param algorithm algorithm 484 * @return a MessageDigestAuthenticator 485 */ 486 private Authenticator createMessageDigestAuthenticator( 487 YamlMapAccessor httpServerAuthenticationBasicYamlMapAccessor, 488 String realm, 489 String username, 490 String password, 491 String algorithm) { 492 String salt = 493 httpServerAuthenticationBasicYamlMapAccessor 494 .get("/salt") 495 .map( 496 new ConvertToString( 497 ConfigurationException.supplier( 498 "Invalid configuration for" 499 + " /httpServer/authentication/basic/salt must" 500 + " be a string"))) 501 .map( 502 new ValidateStringIsNotBlank( 503 ConfigurationException.supplier( 504 "Invalid configuration for" 505 + " /httpServer/authentication/basic/salt must" 506 + " not be blank"))) 507 .orElseThrow( 508 ConfigurationException.supplier( 509 "/httpServer/authentication/basic/salt is a required" 510 + " string")); 511 512 try { 513 return new MessageDigestAuthenticator(realm, username, password, algorithm, salt); 514 } catch (GeneralSecurityException e) { 515 throw new ConfigurationException( 516 String.format( 517 "Invalid /httpServer/authentication/basic/algorithm, unsupported" 518 + " algorithm [%s]", 519 algorithm)); 520 } 521 } 522 523 /** 524 * Method to create a PBKDF2Authenticator 525 * 526 * @param httpServerAuthenticationBasicYamlMapAccessor httpServerAuthenticationBasicMapAccessor 527 * @param realm realm 528 * @param username username 529 * @param password password 530 * @param algorithm algorithm 531 * @return a PBKDF2Authenticator 532 */ 533 private Authenticator createPBKDF2Authenticator( 534 YamlMapAccessor httpServerAuthenticationBasicYamlMapAccessor, 535 String realm, 536 String username, 537 String password, 538 String algorithm) { 539 String salt = 540 httpServerAuthenticationBasicYamlMapAccessor 541 .get("/salt") 542 .map( 543 new ConvertToString( 544 ConfigurationException.supplier( 545 "Invalid configuration for" 546 + " /httpServer/authentication/basic/salt must" 547 + " be a string"))) 548 .map( 549 new ValidateStringIsNotBlank( 550 ConfigurationException.supplier( 551 "Invalid configuration for" 552 + " /httpServer/authentication/basic/salt must" 553 + " be not blank"))) 554 .orElseThrow( 555 ConfigurationException.supplier( 556 "/httpServer/authentication/basic/salt is a required" 557 + " string")); 558 559 int iterations = 560 httpServerAuthenticationBasicYamlMapAccessor 561 .get("/iterations") 562 .map( 563 new ConvertToInteger( 564 ConfigurationException.supplier( 565 "Invalid configuration for" 566 + " /httpServer/authentication/basic/iterations" 567 + " must be an integer"))) 568 .map( 569 new ValidateIntegerInRange( 570 1, 571 Integer.MAX_VALUE, 572 ConfigurationException.supplier( 573 "Invalid configuration for" 574 + " /httpServer/authentication/basic/iterations" 575 + " must be between greater than 0"))) 576 .orElse(PBKDF2_ALGORITHM_ITERATIONS.get(algorithm)); 577 578 int keyLength = 579 httpServerAuthenticationBasicYamlMapAccessor 580 .get("/keyLength") 581 .map( 582 new ConvertToInteger( 583 ConfigurationException.supplier( 584 "Invalid configuration for" 585 + " /httpServer/authentication/basic/keyLength" 586 + " must be an integer"))) 587 .map( 588 new ValidateIntegerInRange( 589 1, 590 Integer.MAX_VALUE, 591 ConfigurationException.supplier( 592 "Invalid configuration for" 593 + " /httpServer/authentication/basic/keyLength" 594 + " must be greater than 0"))) 595 .orElse(PBKDF2_KEY_LENGTH_BITS); 596 597 try { 598 return new PBKDF2Authenticator( 599 realm, username, password, algorithm, salt, iterations, keyLength); 600 } catch (GeneralSecurityException e) { 601 throw new ConfigurationException( 602 String.format( 603 "Invalid /httpServer/authentication/basic/algorithm, unsupported" 604 + " algorithm [%s]", 605 algorithm)); 606 } 607 } 608 609 /** 610 * Method to configure SSL 611 * 612 * @param httpServerBuilder httpServerBuilder 613 */ 614 public void configureSSL(HTTPServer.Builder httpServerBuilder) { 615 if (rootYamlMapAccessor.containsPath("/httpServer/ssl")) { 616 try { 617 String keyStoreFilename = 618 rootYamlMapAccessor 619 .get("/httpServer/ssl/keyStore/filename") 620 .map( 621 new ConvertToString( 622 ConfigurationException.supplier( 623 "Invalid configuration for" 624 + " /httpServer/ssl/keyStore/filename" 625 + " must be a string"))) 626 .map( 627 new ValidateStringIsNotBlank( 628 ConfigurationException.supplier( 629 "Invalid configuration for" 630 + " /httpServer/ssl/keyStore/filename" 631 + " must not be blank"))) 632 .orElse(System.getProperty(JAVAX_NET_SSL_KEY_STORE)); 633 634 String keyStorePassword = 635 rootYamlMapAccessor 636 .get("/httpServer/ssl/keyStore/password") 637 .map( 638 new ConvertToString( 639 ConfigurationException.supplier( 640 "Invalid configuration for" 641 + " /httpServer/ssl/keyStore/password" 642 + " must be a string"))) 643 .map( 644 new ValidateStringIsNotBlank( 645 ConfigurationException.supplier( 646 "Invalid configuration for" 647 + " /httpServer/ssl/keyStore/password" 648 + " must not be blank"))) 649 .orElse(System.getProperty(JAVAX_NET_SSL_KEY_STORE_PASSWORD)); 650 651 String certificateAlias = 652 rootYamlMapAccessor 653 .get("/httpServer/ssl/certificate/alias") 654 .map( 655 new ConvertToString( 656 ConfigurationException.supplier( 657 "Invalid configuration for" 658 + " /httpServer/ssl/certificate/alias" 659 + " must be a string"))) 660 .map( 661 new ValidateStringIsNotBlank( 662 ConfigurationException.supplier( 663 "Invalid configuration for" 664 + " /httpServer/ssl/certificate/alias" 665 + " must not be blank"))) 666 .orElseThrow( 667 ConfigurationException.supplier( 668 "/httpServer/ssl/certificate/alias is a required" 669 + " string")); 670 671 httpServerBuilder.httpsConfigurator( 672 new HttpsConfigurator( 673 SSLContextFactory.createSSLContext( 674 keyStoreFilename, keyStorePassword, certificateAlias))); 675 } catch (GeneralSecurityException | IOException e) { 676 String message = e.getMessage(); 677 if (message != null && !message.trim().isEmpty()) { 678 message = ", " + message.trim(); 679 } else { 680 message = ""; 681 } 682 683 throw new ConfigurationException( 684 String.format("Exception loading SSL configuration%s", message), e); 685 } 686 } 687 } 688 689 /** 690 * Class to implement a named thread factory 691 * 692 * <p>Copied from the `prometheus/client_java` `HTTPServer` due to scoping issues / dependencies 693 */ 694 private static class NamedDaemonThreadFactory implements ThreadFactory { 695 696 private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); 697 698 private final int poolNumber = POOL_NUMBER.getAndIncrement(); 699 private final AtomicInteger threadNumber = new AtomicInteger(1); 700 private final ThreadFactory delegate; 701 private final boolean daemon; 702 703 NamedDaemonThreadFactory(ThreadFactory delegate, boolean daemon) { 704 this.delegate = delegate; 705 this.daemon = daemon; 706 } 707 708 @Override 709 public Thread newThread(Runnable r) { 710 Thread t = delegate.newThread(r); 711 t.setName( 712 String.format( 713 "prometheus-http-%d-%d", poolNumber, threadNumber.getAndIncrement())); 714 t.setDaemon(daemon); 715 return t; 716 } 717 718 static ThreadFactory defaultThreadFactory(boolean daemon) { 719 return new NamedDaemonThreadFactory(Executors.defaultThreadFactory(), daemon); 720 } 721 } 722 723 /** Class to implement a blocking RejectedExecutionHandler */ 724 private static class BlockingRejectedExecutionHandler implements RejectedExecutionHandler { 725 726 @Override 727 public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) { 728 if (!threadPoolExecutor.isShutdown()) { 729 try { 730 threadPoolExecutor.getQueue().put(runnable); 731 } catch (InterruptedException e) { 732 // DO NOTHING 733 } 734 } 735 } 736 } 737}