001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2019 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 014 * implied. See the License for the specific language governing 015 * permissions and limitations under the License. 016 */ 017package org.microbean.jersey.netty.cdi; 018 019import java.lang.annotation.Annotation; 020 021import java.lang.reflect.Type; 022 023import java.net.URI; 024import java.net.URISyntaxException; 025 026import java.nio.channels.spi.SelectorProvider; 027 028import java.util.ArrayList; 029import java.util.Collection; 030import java.util.HashMap; 031import java.util.LinkedHashSet; 032import java.util.Map; 033import java.util.Objects; 034import java.util.Set; 035 036import java.util.concurrent.CountDownLatch; 037import java.util.concurrent.Executor; 038 039import java.util.logging.Handler; 040import java.util.logging.Level; 041import java.util.logging.LogRecord; 042import java.util.logging.Logger; 043 044import javax.enterprise.context.ApplicationScoped; 045import javax.enterprise.context.BeforeDestroyed; 046import javax.enterprise.context.Initialized; 047 048import javax.enterprise.context.spi.CreationalContext; 049 050import javax.enterprise.event.Observes; 051 052import javax.enterprise.inject.Any; 053import javax.enterprise.inject.Default; 054 055import javax.enterprise.inject.spi.Annotated; 056import javax.enterprise.inject.spi.Bean; 057import javax.enterprise.inject.spi.BeanManager; 058import javax.enterprise.inject.spi.DeploymentException; 059import javax.enterprise.inject.spi.Extension; 060 061import javax.enterprise.util.TypeLiteral; 062 063import javax.inject.Named; 064 065import javax.ws.rs.ApplicationPath; 066 067import javax.ws.rs.core.Application; 068 069import io.netty.bootstrap.ServerBootstrap; 070import io.netty.bootstrap.ServerBootstrapConfig; 071 072import io.netty.buffer.Unpooled; 073 074import io.netty.channel.ChannelFactory; 075import io.netty.channel.ChannelOption; 076import io.netty.channel.DefaultSelectStrategyFactory; 077import io.netty.channel.EventLoopGroup; 078import io.netty.channel.SelectStrategyFactory; 079import io.netty.channel.ServerChannel; 080 081import io.netty.channel.epoll.Epoll; 082import io.netty.channel.epoll.EpollChannelOption; 083import io.netty.channel.epoll.EpollEventLoopGroup; 084import io.netty.channel.epoll.EpollServerSocketChannel; 085 086import io.netty.channel.kqueue.KQueue; 087import io.netty.channel.kqueue.KQueueEventLoopGroup; 088import io.netty.channel.kqueue.KQueueServerSocketChannel; 089 090import io.netty.channel.nio.NioEventLoopGroup; 091 092import io.netty.channel.socket.nio.NioServerSocketChannel; 093 094import io.netty.handler.ssl.SslContext; 095 096import io.netty.util.concurrent.DefaultEventExecutorChooserFactory; 097import io.netty.util.concurrent.DefaultEventExecutorGroup; 098import io.netty.util.concurrent.EventExecutorChooserFactory; 099import io.netty.util.concurrent.EventExecutorGroup; 100import io.netty.util.concurrent.Future; 101import io.netty.util.concurrent.RejectedExecutionHandler; 102import io.netty.util.concurrent.RejectedExecutionHandlers; 103 104import org.glassfish.jersey.server.ApplicationHandler; 105 106import org.microbean.configuration.api.Configurations; 107 108import org.microbean.jaxrs.cdi.JaxRsExtension; 109 110import org.microbean.jersey.netty.JerseyChannelInitializer; 111 112/** 113 * A CDI {@linkplain Extension portable extension} that effectively 114 * puts a <a href="https://netty.io/" 115 * target="_parent">Netty</a>-fronted <a 116 * href="https://jersey.github.io/" target="_parent">Jersey</a> 117 * container inside the {@linkplain 118 * javax.enterprise.inject.se.SeContainer CDI container}. 119 * 120 * @author <a href="https://about.me/lairdnelson" 121 * target="_parent">Laird Nelson</a> 122 */ 123public class JerseyNettyExtension implements Extension { 124 125 126 /* 127 * Static fields. 128 */ 129 130 131 private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; 132 133 private static final String cn = JerseyNettyExtension.class.getName(); 134 135 private static final Logger logger = Logger.getLogger(cn); 136 137 138 /* 139 * Instance fields. 140 */ 141 142 143 private final Collection<Throwable> shutdownProblems; 144 145 private volatile Collection<EventExecutorGroup> eventExecutorGroups; 146 147 private volatile CountDownLatch bindLatch; 148 149 private volatile CountDownLatch runLatch; 150 151 private volatile CountDownLatch shutdownLatch; 152 153 private volatile CreationalContext<?> cc; 154 155 156 /* 157 * Constructors. 158 */ 159 160 161 /** 162 * Creates a new {@link JerseyNettyExtension}. 163 * 164 * <p>Instances of this class are normally created by CDI, not by an 165 * end user.</p> 166 */ 167 public JerseyNettyExtension() { 168 super(); 169 170 final Thread containerThread = Thread.currentThread(); 171 workAroundLoggingBug(containerThread); 172 173 if (logger.isLoggable(Level.FINER)) { 174 logger.entering(cn, "<init>"); 175 } 176 177 // If we are running on Weld, prevent its shutdown hook from being 178 // installed. We install a shutdown hook and do some blocking 179 // stuff in @BeforeDestroyed(ApplicationScoped.class). 180 System.setProperty("org.jboss.weld.se.shutdownHook", "false"); 181 182 this.shutdownProblems = new ArrayList<>(); 183 184 Runtime.getRuntime().addShutdownHook(new Thread(() -> { 185 if (logger.isLoggable(Level.FINER)) { 186 logger.entering(JerseyNettyExtension.this.getClass().getName(), "<shutdown hook>"); 187 } 188 189 // Release any current or future block in 190 // waitForAllServersToStop(). 191 unblock(this.runLatch); 192 193 // Release any current or future block on the bind latch (maybe 194 // we've been CTRL-Ced while we're still starting up). 195 unblock(this.bindLatch); 196 197 // We deliberately do not touch the shutdownLatch. That latch 198 // unblocks once Netty has shut down gracefully. 199 200 // Join this shutdown hook thread with the container thread now 201 // that the container has begun its graceful shutdown process. 202 // When a signal is received (which will have triggered this 203 // shutdown hook), then the rules for the VM exiting change a 204 // little bit. Specifically, the VM will exit when all 205 // non-daemon shutdown hook threads are done, not when the main 206 // thread is done (after all, normally CTRL-C will prematurely 207 // terminate the main thread). So to preserve graceful shutdown 208 // we need to hang around until Netty has shut down gracefully. 209 try { 210 if (logger.isLoggable(Level.FINE)) { 211 logger.logp(Level.FINE, 212 cn, 213 "<shutdown hook>", 214 "Waiting for CDI container thread to complete"); 215 } 216 containerThread.join(); 217 } catch (final InterruptedException interruptedException) { 218 Thread.currentThread().interrupt(); 219 } 220 221 if (logger.isLoggable(Level.FINER)) { 222 logger.exiting(JerseyNettyExtension.this.getClass().getName(), "<shutdown hook>"); 223 } 224 })); 225 226 if (logger.isLoggable(Level.FINER)) { 227 logger.exiting(cn, "<init>"); 228 } 229 } 230 231 232 /* 233 * Instance methods. 234 */ 235 236 237 /** 238 * Creates a {@link Logger} when invoked. 239 * 240 * <p>The default implementation of this method returns the result 241 * of invoking {@link Logger#getLogger(String) 242 * Logger.getLogger(this.getClass().getName())}.</p> 243 * 244 * <p>This method never returns {@code null}.</p> 245 * 246 * <p>Overrides of this method must not return {@code null}.</p> 247 * 248 * @return a non-{@code null} {@link Logger} 249 */ 250 protected Logger createLogger() { 251 return Logger.getLogger(this.getClass().getName()); 252 } 253 254 // TODO: prioritize; should "come up" last so traffic isn't accepted 255 // until all "onStartup" observer methods have run 256 private final void onStartup(@Observes 257 @Initialized(ApplicationScoped.class) 258 final Object event, 259 final BeanManager beanManager) 260 throws URISyntaxException { 261 final String mn = "onStartup"; 262 if (logger.isLoggable(Level.FINER)) { 263 logger.entering(cn, mn, new Object[] { event, beanManager }); 264 } 265 Objects.requireNonNull(beanManager); 266 267 final JaxRsExtension extension = beanManager.getExtension(JaxRsExtension.class); 268 assert extension != null; // ...or an IllegalArgumentException will have been thrown per contract 269 270 final Set<Set<Annotation>> applicationQualifierSets = extension.getAllApplicationQualifiers(); 271 if (applicationQualifierSets != null && !applicationQualifierSets.isEmpty()) { 272 273 final CreationalContext<?> cc = beanManager.createCreationalContext(null); 274 assert cc != null; 275 this.cc = cc; 276 277 final Configurations configurations = acquire(beanManager, cc, Configurations.class); 278 assert configurations != null; 279 280 final Map<String, String> baseConfigurationCoordinates = configurations.getConfigurationCoordinates(); 281 282 final int size = applicationQualifierSets.size(); 283 assert size > 0; 284 this.shutdownLatch = new CountDownLatch(size); 285 this.bindLatch = new CountDownLatch(size); 286 final Collection<Throwable> bindProblems = new ArrayList<>(); 287 288 for (final Set<Annotation> applicationQualifiers : applicationQualifierSets) { 289 290 // Quick check to bail out if someone CTRL-Ced and caused 291 // Weld's shutdown hook to fire. That hook fires 292 // a @BeforeDestroyed event on a Thread that is not this 293 // Thread, so it's possible to be processing 294 // an @Initialized(ApplicationScoped.class) event on the 295 // container thread while also processing 296 // a @BeforeDestroyed(ApplicationScoped.class) event. We 297 // basically want to skip bootstrapping a bunch of things if 298 // we're going down anyway. 299 // 300 // Be particularly mindful later on of the state of the 301 // latches. 302 if (Thread.currentThread().isInterrupted()) { 303 if (logger.isLoggable(Level.FINE)) { 304 logger.logp(Level.FINE, cn, mn, "Not binding because the current thread has been interrupted"); 305 } 306 unblock(this.bindLatch); 307 this.bindLatch = null; 308 break; 309 } 310 311 try { 312 313 // Figure out what our microBean Configuration coordinates are. 314 final Map<String, String> qualifierCoordinates = toConfigurationCoordinates(applicationQualifiers); 315 final Map<String, String> configurationCoordinates; 316 if (baseConfigurationCoordinates == null || baseConfigurationCoordinates.isEmpty()) { 317 if (qualifierCoordinates == null || qualifierCoordinates.isEmpty()) { 318 configurationCoordinates = baseConfigurationCoordinates; 319 } else { 320 configurationCoordinates = qualifierCoordinates; 321 } 322 } else if (qualifierCoordinates == null || qualifierCoordinates.isEmpty()) { 323 configurationCoordinates = baseConfigurationCoordinates; 324 } else { 325 configurationCoordinates = new HashMap<>(baseConfigurationCoordinates); 326 configurationCoordinates.putAll(qualifierCoordinates); 327 } 328 329 // Convert the Set of qualifiers into an array. 330 final Annotation[] applicationQualifiersArray; 331 if (applicationQualifiers == null) { 332 applicationQualifiersArray = null; 333 } else if (applicationQualifiers.isEmpty()) { 334 applicationQualifiersArray = EMPTY_ANNOTATION_ARRAY; 335 } else { 336 applicationQualifiersArray = applicationQualifiers.toArray(new Annotation[applicationQualifiers.size()]); 337 } 338 339 // Get the ServerBootstrap that represents the actual web 340 // server. 341 final ServerBootstrap serverBootstrap = 342 getServerBootstrap(beanManager, cc, applicationQualifiersArray, configurations, configurationCoordinates); 343 assert serverBootstrap != null; 344 345 // Get the JerseyChannelInitializer that represents the 346 // actual Jersey integration. 347 final JerseyChannelInitializer jerseyChannelInitializer = 348 getJerseyChannelInitializer(beanManager, cc, applicationQualifiersArray, configurations, configurationCoordinates); 349 assert jerseyChannelInitializer != null; 350 351 // Install the JerseyChannelInitializer so that it is in 352 // charge of handling Jersey-bound requests. 353 serverBootstrap.childHandler(jerseyChannelInitializer); 354 355 // Validate the whole shooting match. 356 serverBootstrap.validate(); 357 358 // Make sure that we count down the shutdownLatch if and 359 // when the server comes down, either properly or 360 // programmatically. 361 final ServerBootstrapConfig serverBootstrapConfig = serverBootstrap.config(); 362 assert serverBootstrapConfig != null; 363 final EventLoopGroup eventLoopGroup = serverBootstrapConfig.group(); 364 assert eventLoopGroup != null; // see serverBootstrap.validate() above, which guarantees this 365 eventLoopGroup.terminationFuture() 366 .addListener(f -> { 367 try { 368 if (f.isSuccess()) { 369 if (logger.isLoggable(Level.FINE)) { 370 logger.logp(Level.FINE, cn, "<ChannelFuture listener>", "EventLoopGroup terminated successfully"); 371 } 372 } else { 373 if (logger.isLoggable(Level.FINE)) { 374 logger.logp(Level.FINE, cn, "<ChannelFuture listener>", "EventLoopGroup terminated with problems: {0}", f.cause()); 375 } 376 final Throwable throwable = f.cause(); 377 if (throwable != null) { 378 synchronized (this.shutdownProblems) { 379 this.shutdownProblems.add(throwable); 380 } 381 } 382 } 383 } finally { 384 if (logger.isLoggable(Level.FINE)) { 385 logger.logp(Level.FINE, cn, "<ChannelFuture listener>", "Counting down shutdownLatch"); 386 } 387 // See the waitForAllServersToStop() method below 388 // which calls await() on this latch. 389 this.shutdownLatch.countDown(); 390 } 391 }); 392 393 // Keep track of all the various EventExecutorGroups in play 394 // so we can stop them and shut them down as necessary 395 // later. 396 final EventExecutorGroup jerseyEventExecutorGroup = jerseyChannelInitializer.getJerseyEventExecutorGroup(); 397 assert jerseyEventExecutorGroup != null; 398 final Collection<EventExecutorGroup> eventExecutorGroups = this.eventExecutorGroups; 399 if (eventExecutorGroups == null) { 400 final Collection<EventExecutorGroup> newEventExecutorGroups = new LinkedHashSet<>(); 401 newEventExecutorGroups.add(eventLoopGroup); 402 newEventExecutorGroups.add(jerseyEventExecutorGroup); 403 this.eventExecutorGroups = newEventExecutorGroups; 404 } else { 405 synchronized (eventExecutorGroups) { 406 eventExecutorGroups.add(eventLoopGroup); 407 eventExecutorGroups.add(jerseyEventExecutorGroup); 408 } 409 } 410 411 // Actually start ("bind") the server. 412 final Future<?> bindFuture; 413 if (Thread.currentThread().isInterrupted()) { 414 // Don't bother binding. 415 bindFuture = null; 416 final CountDownLatch bindLatch = this.bindLatch; 417 if (bindLatch != null) { 418 unblock(bindLatch); 419 this.bindLatch = null; 420 } 421 } else if (serverBootstrapConfig.localAddress() == null) { 422 final URI baseUri = jerseyChannelInitializer.getBaseUri(); 423 bindFuture = serverBootstrap.bind(baseUri.getHost(), baseUri.getPort()); 424 } else { 425 bindFuture = serverBootstrap.bind(); 426 } 427 if (bindFuture != null) { 428 // Binding happened, so make sure we count down the 429 // bindLatch when it is appropriate to do so (during 430 // shutdown). 431 bindFuture.addListener(f -> { 432 try { 433 if (f.isSuccess()) { 434 if (logger.isLoggable(Level.FINE)) { 435 logger.logp(Level.FINE, cn, "<ChannelFuture listener>", "ServerBootstrap bound successfully"); 436 } 437 } else { 438 final Throwable throwable = f.cause(); 439 if (logger.isLoggable(Level.WARNING)) { 440 logger.logp(Level.WARNING, cn, "<ChannelFuture listener>", "ServerBootstrap binding failed: {0}", throwable); 441 } 442 if (throwable != null) { 443 synchronized (bindProblems) { 444 bindProblems.add(throwable); 445 } 446 } 447 } 448 } finally { 449 final CountDownLatch bindLatch = this.bindLatch; 450 if (bindLatch != null) { 451 if (logger.isLoggable(Level.FINE)) { 452 logger.logp(Level.FINE, cn, "<ChannelFuture listener>", "Counting down bindLatch"); 453 } 454 bindLatch.countDown(); 455 } 456 } 457 }); 458 } 459 460 } catch (final RuntimeException | URISyntaxException throwMe) { 461 // If there was a problem, we're going to rethrow it, but 462 // first we have to make sure that we unblock all the 463 // latches involved. 464 unblock(this.bindLatch); 465 unblock(this.shutdownLatch); 466 synchronized (bindProblems) { 467 for (final Throwable bindProblem : bindProblems) { 468 throwMe.addSuppressed(bindProblem); 469 } 470 } 471 cc.release(); 472 throw throwMe; 473 } 474 475 } 476 477 CountDownLatch bindLatch = this.bindLatch; 478 if (!Thread.currentThread().isInterrupted() && bindLatch != null && bindLatch.getCount() > 0) { 479 try { 480 if (logger.isLoggable(Level.FINE)) { 481 logger.logp(Level.FINE, cn, mn, "Awaiting bindLatch"); 482 } 483 // Wait for all the servers to bind to their sockets before 484 // unblocking and letting the CDI container thread go about 485 // its business. 486 bindLatch.await(); 487 if (logger.isLoggable(Level.FINE)) { 488 logger.logp(Level.FINE, cn, mn, "bindLatch released"); 489 } 490 } catch (final InterruptedException interruptedException) { 491 if (logger.isLoggable(Level.FINE)) { 492 logger.logp(Level.FINE, cn, mn, "bindLatch.await() interrupted"); 493 } 494 Thread.currentThread().interrupt(); 495 unblock(bindLatch); 496 bindLatch = null; 497 this.bindLatch = null; 498 } 499 } 500 501 DeploymentException throwMe = null; 502 synchronized (bindProblems) { 503 for (final Throwable bindProblem : bindProblems) { 504 if (throwMe == null) { 505 throwMe = new DeploymentException(bindProblem); 506 } else { 507 throwMe.addSuppressed(bindProblem); 508 } 509 } 510 bindProblems.clear(); 511 } 512 if (throwMe != null) { 513 unblock(this.shutdownLatch); 514 cc.release(); 515 throw throwMe; 516 } 517 518 if (bindLatch != null) { 519 // If we get here with a non-null bindLatch, then a binding 520 // attempt was made and allowed to run to completion. The 521 // completion was successful, because otherwise we would have 522 // thrown an exception. 523 assert bindLatch.getCount() <= 0; 524 this.bindLatch = null; 525 526 // Since we know that binding happened and wasn't interrupted 527 // and didn't fail, we can now create the runLatch, which will 528 // be awaited in the waitForAllServersToStop() method below. 529 if (logger.isLoggable(Level.FINE)) { 530 logger.logp(Level.FINE, cn, mn, "Creating runLatch"); 531 } 532 this.runLatch = new CountDownLatch(1); 533 } 534 535 } 536 assert this.bindLatch == null; 537 538 if (logger.isLoggable(Level.FINER)) { 539 logger.exiting(cn, mn); 540 } 541 } 542 543 // TODO: prioritize; should "fire first" so that other 544 // objects' @BeforeDestroyed(ApplicationScoped.class) observer 545 // methods don't fire prematurely 546 private final void waitForAllServersToStop(@Observes 547 @BeforeDestroyed(ApplicationScoped.class) 548 final Object event) { 549 final String mn = "waitForAllServersToStop"; 550 if (logger.isLoggable(Level.FINER)) { 551 logger.entering(cn, mn, event); 552 } 553 554 // Note: Weld can fire a @BeforeDestroyed event on a thread that 555 // is not the container thread: a shutdown hook that it installs. 556 // So we have to take care to be thread safe. 557 558 CountDownLatch runLatch = this.runLatch; 559 if (runLatch != null) { 560 if (Thread.currentThread().isInterrupted()) { 561 unblock(runLatch); 562 } else { 563 try { 564 if (logger.isLoggable(Level.FINE)) { 565 logger.logp(Level.FINE, cn, mn, "Awaiting runLatch"); 566 } 567 // The servers have all bound to their sockets. The CDI 568 // container thread has run through its lifecycle and is now 569 // in the very first stages of going down. We block here 570 // until someone hits CTRL-C, basically. 571 runLatch.await(); 572 if (logger.isLoggable(Level.FINE)) { 573 logger.logp(Level.FINE, cn, mn, "runLatch released"); 574 } 575 } catch (final InterruptedException interruptedException) { 576 if (logger.isLoggable(Level.FINE)) { 577 logger.logp(Level.FINE, cn, mn, "runLatch.await() interrupted"); 578 } 579 Thread.currentThread().interrupt(); 580 if (logger.isLoggable(Level.FINE)) { 581 logger.logp(Level.FINE, cn, mn, "Unblocking runLatch"); 582 } 583 unblock(runLatch); 584 } 585 } 586 assert runLatch.getCount() <= 0; 587 runLatch = null; 588 this.runLatch = null; 589 } 590 591 // Now that someone has CTRL-Ced us, request that all the 592 // EventExecutorGroups gracefully shutdown. 593 final Collection<? extends EventExecutorGroup> eventExecutorGroups = this.eventExecutorGroups; 594 if (eventExecutorGroups != null) { 595 synchronized (eventExecutorGroups) { 596 if (!eventExecutorGroups.isEmpty()) { 597 for (final EventExecutorGroup eventExecutorGroup : eventExecutorGroups) { 598 if (logger.isLoggable(Level.FINE)) { 599 logger.logp(Level.FINE, cn, mn, "Shutting down {0} gracefully", eventExecutorGroup); 600 } 601 // Sends a request to the Netty threads to wait for a 602 // configurable period of time and then shut down 603 // carefully. This call itself does not block. 604 eventExecutorGroup.shutdownGracefully(); 605 } 606 eventExecutorGroups.clear(); 607 } 608 } 609 this.eventExecutorGroups = null; 610 } 611 612 // Now wait for all the EventExecutorGroups to finish shutting down. 613 final CountDownLatch shutdownLatch = this.shutdownLatch; 614 if (shutdownLatch != null) { 615 try { 616 if (logger.isLoggable(Level.FINE)) { 617 logger.logp(Level.FINE, cn, mn, "Awaiting shutdownLatch"); 618 } 619 // Wait for the EventExecutorGroup ChannelFuture to complete 620 // and call shutdownLatch.countDown(). 621 shutdownLatch.await(); 622 if (logger.isLoggable(Level.FINE)) { 623 logger.logp(Level.FINE, cn, mn, "shutdownLatch released"); 624 } 625 } catch (final InterruptedException interruptedException) { 626 if (logger.isLoggable(Level.FINE)) { 627 logger.logp(Level.FINE, cn, mn, "shutdownLatch.await() interrupted"); 628 } 629 Thread.currentThread().interrupt(); 630 } 631 assert shutdownLatch.getCount() <= 0; 632 this.shutdownLatch = null; 633 } 634 635 // Ensure all dependent objects, if there are any, are disposed of 636 // properly. 637 final CreationalContext<?> cc = this.cc; 638 if (cc != null) { 639 cc.release(); 640 } 641 642 // Ensure we don't lose any problems encountered along the long 643 // road of the shutdown proceure. 644 DeploymentException throwMe = null; 645 synchronized (this.shutdownProblems) { 646 for (final Throwable shutdownProblem : this.shutdownProblems) { 647 if (throwMe == null) { 648 throwMe = new DeploymentException(shutdownProblem); 649 } else { 650 throwMe.addSuppressed(shutdownProblem); 651 } 652 } 653 this.shutdownProblems.clear(); 654 } 655 if (throwMe != null) { 656 throw throwMe; 657 } 658 659 if (logger.isLoggable(Level.FINER)) { 660 logger.exiting(cn, mn); 661 } 662 } 663 664 665 /* 666 * Production and lookup methods. 667 */ 668 669 670 private static final Application getApplication(final BeanManager beanManager, 671 final CreationalContext<?> cc, 672 final Annotation[] qualifiersArray) { 673 return acquire(beanManager, cc, Application.class, qualifiersArray); 674 } 675 676 private static final JerseyChannelInitializer getJerseyChannelInitializer(final BeanManager beanManager, 677 final CreationalContext<?> cc, 678 final Annotation[] qualifiersArray, 679 final Configurations configurations, 680 final Map<String, String> configurationCoordinates) throws URISyntaxException { 681 final Application application = getApplication(beanManager, cc, qualifiersArray); 682 683 // See if the Application class has an ApplicationPath annotation 684 // on it. Get its value if so. 685 final String applicationPath; 686 if (application == null) { 687 applicationPath = "/"; 688 } else { 689 final Annotated applicationType = beanManager.createAnnotatedType(application.getClass()); 690 assert applicationType != null; 691 final ApplicationPath applicationPathAnnotation = applicationType.getAnnotation(ApplicationPath.class); 692 if (applicationPathAnnotation == null) { 693 applicationPath = "/"; 694 } else { 695 applicationPath = applicationPathAnnotation.value(); 696 } 697 } 698 assert applicationPath != null; 699 700 final URI baseUri; 701 final String host = configurations.getValue(configurationCoordinates, 702 "host", 703 String.class, 704 "0.0.0.0"); 705 final SslContext sslContext = getSslContext(beanManager, cc, qualifiersArray); 706 if (sslContext == null) { 707 baseUri = 708 new URI("http", 709 null /* no userInfo */, 710 host, 711 configurations.getValue(configurationCoordinates, 712 "port", 713 Integer.TYPE, 714 "8080"), 715 applicationPath, 716 null /* no query */, 717 null /* no fragment */); 718 } else { 719 baseUri = 720 new URI("https", 721 null /* no userInfo */, 722 host, 723 configurations.getValue(configurationCoordinates, 724 "port", 725 Integer.TYPE, 726 "443"), 727 applicationPath, 728 null /* no query */, 729 null /* no fragment */); 730 } 731 assert baseUri != null; 732 733 final EventExecutorGroup jerseyEventExecutorGroup = 734 getJerseyEventExecutorGroup(beanManager, cc, qualifiersArray, configurations, configurationCoordinates); 735 assert jerseyEventExecutorGroup != null; 736 737 final JerseyChannelInitializer returnValue = 738 new JerseyChannelInitializer(baseUri, 739 sslContext, 740 configurations.getValue(configurationCoordinates, 741 "http2Support", 742 Boolean.TYPE, 743 "true"), 744 configurations.getValue(configurationCoordinates, 745 "maxIncomingContentLength", 746 Long.TYPE, 747 String.valueOf(Long.MAX_VALUE)), 748 jerseyEventExecutorGroup, 749 application, 750 configurations.getValue(configurationCoordinates, 751 "flushThreshold", 752 Integer.TYPE, 753 "8192"), 754 Unpooled::wrappedBuffer); 755 return returnValue; 756 } 757 758 759 private static final SslContext getSslContext(final BeanManager beanManager, 760 final CreationalContext<?> cc, 761 final Annotation[] qualifiersArray) { 762 return acquire(beanManager, 763 cc, 764 SslContext.class, 765 qualifiersArray, 766 true, 767 (bm, cctx, qa) -> null); 768 } 769 770 private static final ServerBootstrap getServerBootstrap(final BeanManager beanManager, 771 final CreationalContext<?> cc, 772 final Annotation[] qualifiersArray, 773 final Configurations configurations, 774 final Map<String, String> configurationCoordinates) { 775 return acquire(beanManager, 776 cc, 777 ServerBootstrap.class, 778 qualifiersArray, 779 true, 780 (bm, cctx, qa) -> { 781 final ServerBootstrap returnValue = new ServerBootstrap(); 782 // See https://stackoverflow.com/a/28342821/208288 783 returnValue.group(getEventLoopGroup(bm, cctx, qa, configurations, configurationCoordinates)); 784 returnValue.channelFactory(getChannelFactory(bm, cctx, qa)); 785 // Permit arbitrary customization 786 beanManager.getEvent().select(ServerBootstrap.class, qualifiersArray).fire(returnValue); 787 return returnValue; 788 }); 789 } 790 791 private static final ChannelFactory<? extends ServerChannel> getChannelFactory(final BeanManager beanManager, 792 final CreationalContext<?> cc, 793 final Annotation[] qualifiersArray) { 794 return acquire(beanManager, 795 cc, 796 new TypeLiteral<ChannelFactory<? extends ServerChannel>>() { 797 private static final long serialVersionUID = 1L; 798 }.getType(), 799 qualifiersArray, 800 true, 801 (bm, cctx, qa) -> { 802 if (Epoll.isAvailable()) { 803 return () -> new EpollServerSocketChannel(); 804 } else if (KQueue.isAvailable()) { 805 return () -> new KQueueServerSocketChannel(); 806 } else { 807 return () -> new NioServerSocketChannel(getSelectorProvider(bm, cctx, qa)); 808 } 809 }); 810 } 811 812 private static final EventLoopGroup getEventLoopGroup(final BeanManager beanManager, 813 final CreationalContext<?> cc, 814 final Annotation[] qualifiersArray, 815 final Configurations configurations, 816 final Map<String, String> configurationCoordinates) { 817 return acquire(beanManager, 818 cc, 819 EventLoopGroup.class, 820 qualifiersArray, 821 true, 822 (bm, cctx, qa) -> { 823 final EventLoopGroup returnValue; 824 final int threadCount = configurations.getValue(configurationCoordinates, 825 "io.netty.eventLoopThreads", 826 Integer.TYPE, 827 "0"); // 0 == default number of threads; see https://github.com/netty/netty/blob/8d99aa1235d07376f9df3ae3701692091117725a/transport/src/main/java/io/netty/channel/MultithreadEventLoopGroup.java#L40-L41 828 final Executor executor = getExecutor(bm, cctx, qa); 829 final EventExecutorChooserFactory eventExecutorChooserFactory = getEventExecutorChooserFactory(bm, cctx, qa); 830 final SelectStrategyFactory selectStrategyFactory = getSelectStrategyFactory(bm, cctx, qa); 831 final RejectedExecutionHandler rejectedExecutionHandler = getRejectedExecutionHandler(bm, cctx, qa); 832 if (Epoll.isAvailable()) { 833 returnValue = 834 new EpollEventLoopGroup(threadCount, 835 executor, 836 eventExecutorChooserFactory, 837 selectStrategyFactory, 838 rejectedExecutionHandler); 839 } else if (KQueue.isAvailable()) { 840 returnValue = 841 new KQueueEventLoopGroup(threadCount, 842 executor, 843 eventExecutorChooserFactory, 844 selectStrategyFactory, 845 rejectedExecutionHandler); 846 } else { 847 returnValue = 848 new NioEventLoopGroup(threadCount, 849 executor, 850 eventExecutorChooserFactory, 851 getSelectorProvider(bm, cctx, qa), 852 selectStrategyFactory, 853 rejectedExecutionHandler); 854 } 855 // Permit arbitrary customization. (Not much you can do here 856 // except call setIoRatio(int).) 857 beanManager.getEvent().select(EventLoopGroup.class, qa).fire(returnValue); 858 return returnValue; 859 }); 860 } 861 862 private static final Executor getExecutor(final BeanManager beanManager, 863 final CreationalContext<?> cc, 864 final Annotation[] qualifiersArray) { 865 return acquire(beanManager, 866 cc, 867 Executor.class, 868 qualifiersArray); 869 } 870 871 private static final RejectedExecutionHandler getRejectedExecutionHandler(final BeanManager beanManager, 872 final CreationalContext<?> cc, 873 final Annotation[] qualifiersArray) { 874 return acquire(beanManager, 875 cc, 876 RejectedExecutionHandler.class, 877 qualifiersArray, 878 true, 879 (bm, cctx, qa) -> RejectedExecutionHandlers.reject()); 880 } 881 882 private static final SelectorProvider getSelectorProvider(final BeanManager beanManager, 883 final CreationalContext<?> cc, 884 final Annotation[] qualifiersArray) { 885 return acquire(beanManager, 886 cc, 887 SelectorProvider.class, 888 qualifiersArray, 889 true, 890 (bm, cctx, qa) -> SelectorProvider.provider()); 891 } 892 893 private static final SelectStrategyFactory getSelectStrategyFactory(final BeanManager beanManager, 894 final CreationalContext<?> cc, 895 final Annotation[] qualifiersArray) { 896 return acquire(beanManager, 897 cc, 898 SelectStrategyFactory.class, 899 qualifiersArray, 900 true, 901 (bm, cctx, qa) -> DefaultSelectStrategyFactory.INSTANCE); 902 } 903 904 private static final EventExecutorChooserFactory getEventExecutorChooserFactory(final BeanManager beanManager, 905 final CreationalContext<?> cc, 906 final Annotation[] qualifiersArray) { 907 return acquire(beanManager, 908 cc, 909 EventExecutorChooserFactory.class, 910 qualifiersArray, 911 true, 912 (bm, cctx, qa) -> DefaultEventExecutorChooserFactory.INSTANCE); 913 } 914 915 private static final EventExecutorGroup getJerseyEventExecutorGroup(final BeanManager beanManager, 916 final CreationalContext<?> cc, 917 final Annotation[] qualifiersArray, 918 final Configurations configurations, 919 final Map<String, String> configurationCoordinates) { 920 return acquire(beanManager, 921 cc, 922 EventExecutorGroup.class, 923 qualifiersArray, 924 true, 925 (bm, cctx, qa) -> new DefaultEventExecutorGroup(configurations.getValue(configurationCoordinates, 926 "jerseyThreads", 927 Integer.TYPE, 928 String.valueOf(2 * Runtime.getRuntime().availableProcessors())))); 929 } 930 931 932 /* 933 * Static utility methods. 934 */ 935 936 937 private static final <T> T acquire(final BeanManager beanManager, 938 final CreationalContext<?> cc, 939 final Type type) { 940 return acquire(beanManager, cc, type, null, false, (bm, cctx, qa) -> null); 941 } 942 943 private static final <T> T acquire(final BeanManager beanManager, 944 final CreationalContext<?> cc, 945 final Type type, 946 final Annotation[] qualifiersArray) { 947 return acquire(beanManager, cc, type, qualifiersArray, false, (bm, cctx, qa) -> null); 948 } 949 950 private static final <T> T acquire(final BeanManager beanManager, 951 final CreationalContext<?> cc, 952 final Type type, 953 final Annotation[] qualifiersArray, 954 final boolean fallbackWithDefaultQualifier) { 955 return acquire(beanManager, cc, type, qualifiersArray, fallbackWithDefaultQualifier, (bm, cctx, qa) -> null); 956 } 957 958 private static final <T> T acquire(final BeanManager beanManager, 959 final CreationalContext<?> cc, 960 final Type type, 961 final Annotation[] qualifiersArray, 962 final boolean fallbackWithDefaultQualifier, 963 final DefaultValueFunction<? extends T> defaultValueFunction) { 964 Objects.requireNonNull(beanManager); 965 Objects.requireNonNull(type); 966 Objects.requireNonNull(defaultValueFunction); 967 968 final T returnValue; 969 970 Set<Bean<?>> beans = null; 971 if (qualifiersArray == null || qualifiersArray.length <= 0 || (qualifiersArray.length == 1 && qualifiersArray[0] instanceof Default)) { 972 beans = beanManager.getBeans(type); 973 } else { 974 beans = beanManager.getBeans(type, qualifiersArray); 975 if (fallbackWithDefaultQualifier && (beans == null || beans.isEmpty())) { 976 beans = beanManager.getBeans(type); 977 } 978 } 979 if (beans == null || beans.isEmpty()) { 980 returnValue = defaultValueFunction.getDefaultValue(beanManager, cc, qualifiersArray); 981 } else { 982 final Bean<?> bean = beanManager.resolve(beans); 983 if (bean == null) { 984 returnValue = defaultValueFunction.getDefaultValue(beanManager, cc, qualifiersArray); 985 } else { 986 @SuppressWarnings("unchecked") 987 final T temp = (T)beanManager.getReference(bean, type, cc); 988 returnValue = temp; 989 } 990 } 991 992 return returnValue; 993 } 994 995 private static final void unblock(final CountDownLatch latch) { 996 if (latch != null) { 997 while (latch.getCount() > 0L) { 998 latch.countDown(); 999 } 1000 } 1001 } 1002 1003 private static final Map<String, String> toConfigurationCoordinates(final Set<? extends Annotation> qualifiers) { 1004 final Map<String, String> returnValue = new HashMap<>(); 1005 if (qualifiers != null && !qualifiers.isEmpty()) { 1006 for (final Annotation qualifier : qualifiers) { 1007 if (qualifier instanceof Named) { 1008 returnValue.put("name", ((Named)qualifier).value()); 1009 } else if (!(qualifier instanceof Default) && !(qualifier instanceof Any)) { 1010 returnValue.put(qualifier.toString(), ""); 1011 } 1012 } 1013 } 1014 return returnValue; 1015 } 1016 1017 /** 1018 * Works around <a 1019 * href="https://bugs.openjdk.java.net/browse/JDK-8029834" 1020 * target="_parent">JDK-8029834</a>, which can cause logging to go 1021 * dark during shutdown. 1022 * 1023 * <p>This method should be called only once.</p> 1024 * 1025 * @param containerThread the {@link Thread} on which the CDI 1026 * container's "main loop" is running; must not be {@code null} 1027 * 1028 * @exception NullPointerException if {@code containerThread} is 1029 * {@code null} 1030 * 1031 * @see <a href="https://bugs.openjdk.java.net/browse/JDK-8029834" 1032 * target="_parent">JDK-8029834</a> 1033 * 1034 * @see <a href="https://bugs.openjdk.java.net/browse/JDK-8161253" 1035 * target="_parent">JDK-8161253</a> 1036 * 1037 * @see <a 1038 * href="https://github.com/openjdk/jdk/blob/6734ec66c34642920788c86b62a36e4195676b6d/src/java.logging/share/classes/java/util/logging/LogManager.java#L256-L284" 1039 * target="_parent">the {@code LogManager} {@code Cleaner} inner 1040 * class source code</a> 1041 */ 1042 private static final void workAroundLoggingBug(final Thread containerThread) { 1043 // In the code that follows, we install a NoOpHandler (a private 1044 // nested class defined at the bottom of this file) as the first 1045 // Handler on the root Logger. This special Handler's close() 1046 // method joins with the container thread, and so when that 1047 // close() method is called by the LogManager cleaner as a result 1048 // of a shutdown hook, it will block actual closing of that 1049 // handler and all others until the main thread has completed. 1050 final Logger rootLogger = Logger.getLogger(""); 1051 assert rootLogger != null; 1052 final Handler[] rootHandlers = rootLogger.getHandlers(); 1053 final boolean rootHandlersIsEmpty = rootHandlers == null || rootHandlers.length <= 0; 1054 if (!rootHandlersIsEmpty) { 1055 for (final Handler rootHandler : rootHandlers) { 1056 rootLogger.removeHandler(rootHandler); 1057 } 1058 } 1059 rootLogger.addHandler(new NoOpHandler(containerThread)); 1060 if (!rootHandlersIsEmpty) { 1061 for (final Handler rootHandler : rootHandlers) { 1062 rootLogger.addHandler(rootHandler); 1063 } 1064 } 1065 } 1066 1067 1068 /* 1069 * Inner and nested classes. 1070 */ 1071 1072 1073 @FunctionalInterface 1074 private static interface DefaultValueFunction<T> { 1075 1076 T getDefaultValue(final BeanManager beanManager, 1077 final CreationalContext<?> cc, 1078 final Annotation[] qualifiersArray); 1079 1080 } 1081 1082 private static final class NoOpHandler extends Handler { 1083 1084 private final Thread thread; 1085 1086 private NoOpHandler(final Thread t) { 1087 super(); 1088 this.thread = Objects.requireNonNull(t); 1089 } 1090 1091 @Override 1092 public final void close() { 1093 try { 1094 this.thread.join(); 1095 } catch (final InterruptedException interruptedException) { 1096 Thread.currentThread().interrupt(); 1097 } 1098 } 1099 1100 @Override 1101 public final void flush() { 1102 1103 } 1104 1105 @Override 1106 public final void publish(final LogRecord logRecord) { 1107 1108 } 1109 1110 } 1111 1112}