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.Map; 032import java.util.Objects; 033import java.util.Set; 034 035import java.util.concurrent.CountDownLatch; 036import java.util.concurrent.Executor; 037 038import java.util.logging.Handler; 039import java.util.logging.Level; 040import java.util.logging.LogRecord; 041import java.util.logging.Logger; 042 043import javax.enterprise.context.ApplicationScoped; 044import javax.enterprise.context.BeforeDestroyed; 045import javax.enterprise.context.Initialized; 046 047import javax.enterprise.context.spi.CreationalContext; 048 049import javax.enterprise.event.Observes; 050 051import javax.enterprise.inject.Any; 052import javax.enterprise.inject.Default; 053 054import javax.enterprise.inject.spi.Bean; 055import javax.enterprise.inject.spi.BeanManager; 056import javax.enterprise.inject.spi.DeploymentException; 057import javax.enterprise.inject.spi.Extension; 058 059import javax.enterprise.util.TypeLiteral; 060 061import javax.inject.Named; 062 063import javax.ws.rs.ApplicationPath; 064 065import javax.ws.rs.core.Application; 066 067import io.netty.bootstrap.ServerBootstrap; 068import io.netty.bootstrap.ServerBootstrapConfig; 069 070import io.netty.channel.ChannelFactory; 071import io.netty.channel.DefaultSelectStrategyFactory; 072import io.netty.channel.EventLoopGroup; 073import io.netty.channel.SelectStrategyFactory; 074import io.netty.channel.ServerChannel; 075 076import io.netty.channel.nio.NioEventLoopGroup; 077 078import io.netty.channel.socket.nio.NioServerSocketChannel; 079 080import io.netty.handler.ssl.SslContext; 081 082import io.netty.util.concurrent.EventExecutorGroup; 083import io.netty.util.concurrent.Future; 084import io.netty.util.concurrent.RejectedExecutionHandler; 085import io.netty.util.concurrent.RejectedExecutionHandlers; 086import io.netty.util.concurrent.EventExecutorChooserFactory; 087import io.netty.util.concurrent.DefaultEventExecutorChooserFactory; 088 089import org.glassfish.jersey.server.ApplicationHandler; 090 091import org.microbean.configuration.api.Configurations; 092 093import org.microbean.jaxrs.cdi.JaxRsExtension; 094 095import org.microbean.jersey.netty.JerseyChannelInitializer; 096 097/** 098 * A CDI {@linkplain Extension portable extension} that effectively 099 * puts a <a href="https://netty.io/" 100 * target="_parent">Netty</a>-fronted <a 101 * href="https://jersey.github.io/" target="_parent">Jersey</a> 102 * container inside the {@linkplain 103 * javax.enterprise.inject.se.SeContainer CDI container}. 104 * 105 * @author <a href="https://about.me/lairdnelson" 106 * target="_parent">Laird Nelson</a> 107 */ 108public class JerseyNettyExtension implements Extension { 109 110 111 /* 112 * Static fields. 113 */ 114 115 116 private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[0]; 117 118 119 /* 120 * Instance fields. 121 */ 122 123 124 private final Logger logger; 125 126 private final Collection<Throwable> shutdownProblems; 127 128 private volatile Collection<EventExecutorGroup> eventExecutorGroups; 129 130 private volatile CountDownLatch bindLatch; 131 132 private volatile CountDownLatch runLatch; 133 134 private volatile CountDownLatch shutdownLatch; 135 136 private volatile CreationalContext<?> cc; 137 138 139 /* 140 * Constructors. 141 */ 142 143 144 /** 145 * Creates a new {@link JerseyNettyExtension}. 146 * 147 * <p>Instances of this class are normally created by CDI, not by an 148 * end user.</p> 149 */ 150 public JerseyNettyExtension() { 151 super(); 152 153 final Thread containerThread = Thread.currentThread(); 154 155 // Laboriously work around 156 // https://bugs.openjdk.java.net/browse/JDK-8029834. 157 // 158 // In the code that follows, we install a NoOpHandler (a private 159 // nested class defined at the bottom of this file) as the first 160 // Handler on the root Logger. This special Handler's close() 161 // method joins with the container thread, and so when that 162 // close() method is called by the LogManager cleaner as a result 163 // of a shutdown hook, it will block actual closing of that 164 // handler and all others until the main thread has completed. 165 // 166 // See the 167 // following links for more details: 168 // * https://bugs.openjdk.java.net/browse/JDK-8161253 169 // * https://bugs.openjdk.java.net/browse/JDK-8029834 170 // * https://github.com/openjdk/jdk/blob/6734ec66c34642920788c86b62a36e4195676b6d/src/java.logging/share/classes/java/util/logging/LogManager.java#L256-L284 171 final Logger rootLogger = Logger.getLogger(""); 172 assert rootLogger != null; 173 final Handler[] rootHandlers = rootLogger.getHandlers(); 174 final boolean rootHandlersIsEmpty = rootHandlers == null || rootHandlers.length <= 0; 175 if (!rootHandlersIsEmpty) { 176 for (final Handler rootHandler : rootHandlers) { 177 rootLogger.removeHandler(rootHandler); 178 } 179 } 180 rootLogger.addHandler(new NoOpHandler(containerThread)); 181 if (!rootHandlersIsEmpty) { 182 for (final Handler rootHandler : rootHandlers) { 183 rootLogger.addHandler(rootHandler); 184 } 185 } 186 187 final String cn = this.getClass().getName(); 188 final Logger logger = this.createLogger(); 189 if (logger == null) { 190 this.logger = Logger.getLogger(cn); 191 } else { 192 this.logger = logger; 193 } 194 if (this.logger.isLoggable(Level.FINER)) { 195 this.logger.entering(cn, "<init>"); 196 } 197 198 // If we are running on Weld, prevent its shutdown hook from being 199 // installed. We install a shutdown hook and do some blocking 200 // stuff in @BeforeDestroyed(ApplicationScoped.class). 201 System.setProperty("org.jboss.weld.se.shutdownHook", "false"); 202 203 this.shutdownProblems = new ArrayList<>(); 204 205 Runtime.getRuntime().addShutdownHook(new Thread(() -> { 206 if (logger.isLoggable(Level.FINER)) { 207 logger.entering(JerseyNettyExtension.this.getClass().getName(), "<shutdown hook>"); 208 } 209 210 // Logging once a shutdown hook has been invoked can just 211 // stop working because the LogManager actually runs its own 212 // shutdown hook in parallel that starts closing handlers. 213 // See the following links for more details: 214 // * https://bugs.openjdk.java.net/browse/JDK-8161253 215 // * https://bugs.openjdk.java.net/browse/JDK-8029834 216 // * https://github.com/openjdk/jdk/blob/6734ec66c34642920788c86b62a36e4195676b6d/src/java.logging/share/classes/java/util/logging/LogManager.java#L256-L284 217 // There is nothing we can do about this. 218 219 // Release any current or future block in 220 // waitForAllServersToStop(). 221 zeroOut(this.runLatch); 222 223 // Release any current or future block on the bind latch 224 // (maybe we've been CTRL-Ced while we're still starting 225 // up). 226 zeroOut(this.bindLatch); 227 228 // We deliberately do not touch the shutdownLatch. That 229 // latch unblocks once Netty has shut down gracefully. 230 231 // Join this shutdown hook thread with the container thread 232 // now that the container has begun its graceful shutdown 233 // process. When a signal is received (which will have 234 // triggered this shutdown hook), then the rules for the VM 235 // exiting change a little bit. Specifically, the VM will 236 // exit when all non-daemon shutdown hook threads are done, 237 // not when the main thread is done (after all, normally 238 // CTRL-C will prematurely terminate the main thread). So 239 // to preserve graceful shutdown we need to hang around 240 // until Netty has shut down gracefully. 241 try { 242 if (logger.isLoggable(Level.FINE)) { 243 logger.logp(Level.FINE, 244 JerseyNettyExtension.this.getClass().getName(), 245 "<shutdown hook>", 246 "Waiting for CDI container thread to complete"); 247 } 248 containerThread.join(); 249 } catch (final InterruptedException interruptedException) { 250 Thread.currentThread().interrupt(); 251 } 252 253 if (logger.isLoggable(Level.FINER)) { 254 logger.exiting(JerseyNettyExtension.this.getClass().getName(), "<shutdown hook>"); 255 } 256 })); 257 258 if (this.logger.isLoggable(Level.FINER)) { 259 this.logger.exiting(cn, "<init>"); 260 } 261 } 262 263 264 /* 265 * Instance methods. 266 */ 267 268 269 /** 270 * Creates a {@link Logger} when invoked. 271 * 272 * <p>The default implementation of this method returns the result 273 * of invoking {@link Logger#getLogger(String) 274 * Logger.getLogger(this.getClass().getName())}.</p> 275 * 276 * <p>This method never returns {@code null}.</p> 277 * 278 * <p>Overrides of this method must not return {@code null}.</p> 279 * 280 * @return a non-{@code null} {@link Logger} 281 */ 282 protected Logger createLogger() { 283 return Logger.getLogger(this.getClass().getName()); 284 } 285 286 // TODO: prioritize; should "come up" last so traffic isn't accepted 287 // until all "onStartup" observer methods have run 288 private final void onStartup(@Observes 289 @Initialized(ApplicationScoped.class) 290 final Object event, 291 final BeanManager beanManager) 292 throws URISyntaxException { 293 final String cn = this.getClass().getName(); 294 final String mn = "onStartup"; 295 if (this.logger.isLoggable(Level.FINER)) { 296 this.logger.entering(cn, mn, new Object[] { event, beanManager }); 297 } 298 Objects.requireNonNull(beanManager); 299 300 final JaxRsExtension extension = beanManager.getExtension(JaxRsExtension.class); 301 assert extension != null; // ...or an IllegalArgumentException will have been thrown per contract 302 303 final Set<Set<Annotation>> applicationQualifierSets = extension.getAllApplicationQualifiers(); 304 if (applicationQualifierSets != null && !applicationQualifierSets.isEmpty()) { 305 306 final CreationalContext<?> cc = beanManager.createCreationalContext(null); 307 assert cc != null; 308 this.cc = cc; 309 310 final Configurations configurations = acquire(beanManager, cc, Configurations.class); 311 assert configurations != null; 312 313 final Map<String, String> baseConfigurationCoordinates = configurations.getConfigurationCoordinates(); 314 315 final int size = applicationQualifierSets.size(); 316 assert size > 0; 317 this.shutdownLatch = new CountDownLatch(size); 318 this.bindLatch = new CountDownLatch(size); 319 final Collection<Throwable> bindProblems = new ArrayList<>(); 320 321 for (final Set<Annotation> applicationQualifiers : applicationQualifierSets) { 322 323 // Quick check to bail out if someone CTRL-Ced and caused 324 // Weld's shutdown hook to fire. That hook fires 325 // a @BeforeDestroyed event on a Thread that is not this 326 // Thread, so it's possible to be processing 327 // an @Initialized(ApplicationScoped.class) event on the 328 // container thread while also processing 329 // a @BeforeDestroyed(ApplicationScoped.class) event. We 330 // basically want to skip bootstrapping a bunch of things if 331 // we're going down anyway. 332 // 333 // Be particularly mindful later on of the state of the 334 // latches. 335 if (Thread.currentThread().isInterrupted()) { 336 if (this.logger.isLoggable(Level.FINE)) { 337 this.logger.logp(Level.FINE, cn, mn, "Not binding because the current thread has been interrupted"); 338 } 339 zeroOut(this.bindLatch); 340 this.bindLatch = null; 341 break; 342 } 343 344 final Annotation[] applicationQualifiersArray; 345 if (applicationQualifiers == null) { 346 applicationQualifiersArray = null; 347 } else if (applicationQualifiers.isEmpty()) { 348 applicationQualifiersArray = EMPTY_ANNOTATION_ARRAY; 349 } else { 350 applicationQualifiersArray = applicationQualifiers.toArray(new Annotation[applicationQualifiers.size()]); 351 } 352 353 final Set<Bean<?>> applicationBeans = beanManager.getBeans(Application.class, applicationQualifiersArray); 354 assert applicationBeans != null; 355 assert !applicationBeans.isEmpty(); 356 357 try { 358 359 @SuppressWarnings("unchecked") 360 final Bean<Application> applicationBean = (Bean<Application>)beanManager.resolve(applicationBeans); 361 assert applicationBean != null; 362 363 final Application application = (Application)beanManager.getReference(applicationBean, Application.class, cc); 364 assert application != null; 365 366 final ApplicationPath applicationPathAnnotation = application.getClass().getAnnotation(ApplicationPath.class); 367 final String applicationPath; 368 if (applicationPathAnnotation == null) { 369 applicationPath = "/"; 370 } else { 371 applicationPath = applicationPathAnnotation.value(); 372 } 373 assert applicationPath != null; 374 375 final ServerBootstrap serverBootstrap = getServerBootstrap(beanManager, cc, applicationQualifiersArray); 376 assert serverBootstrap != null; 377 378 final SslContext sslContext = getSslContext(beanManager, cc, applicationQualifiersArray); 379 380 final Map<String, String> qualifierCoordinates = toConfigurationCoordinates(applicationQualifiers); 381 final Map<String, String> configurationCoordinates; 382 if (baseConfigurationCoordinates == null || baseConfigurationCoordinates.isEmpty()) { 383 if (qualifierCoordinates == null || qualifierCoordinates.isEmpty()) { 384 configurationCoordinates = baseConfigurationCoordinates; 385 } else { 386 configurationCoordinates = qualifierCoordinates; 387 } 388 } else if (qualifierCoordinates == null || qualifierCoordinates.isEmpty()) { 389 configurationCoordinates = baseConfigurationCoordinates; 390 } else { 391 configurationCoordinates = new HashMap<>(baseConfigurationCoordinates); 392 configurationCoordinates.putAll(qualifierCoordinates); 393 } 394 395 final URI baseUri; 396 if (sslContext == null) { 397 baseUri = new URI("http", 398 null /* no userInfo */, 399 configurations.getValue(configurationCoordinates, "host", "0.0.0.0"), 400 configurations.getValue(configurationCoordinates, "port", Integer.TYPE, "8080"), 401 applicationPath, 402 null /* no query */, 403 null /* no fragment */); 404 } else { 405 baseUri = new URI("https", 406 null /* no userInfo */, 407 configurations.getValue(configurationCoordinates, "host", "0.0.0.0"), 408 configurations.getValue(configurationCoordinates, "port", Integer.TYPE, "443"), 409 applicationPath, 410 null /* no query */, 411 null /* no fragment */); 412 } 413 assert baseUri != null; 414 415 serverBootstrap.childHandler(new JerseyChannelInitializer(baseUri, 416 sslContext, 417 new ApplicationHandler(application))); 418 serverBootstrap.validate(); 419 420 final ServerBootstrapConfig serverBootstrapConfig = serverBootstrap.config(); 421 assert serverBootstrapConfig != null; 422 423 final EventLoopGroup eventLoopGroup = serverBootstrapConfig.group(); 424 assert eventLoopGroup != null; // see validate() above 425 eventLoopGroup.terminationFuture() 426 .addListener(f -> { 427 try { 428 if (f.isSuccess()) { 429 if (logger.isLoggable(Level.FINE)) { 430 logger.logp(Level.FINE, cn, "<ChannelFuture listener>", "EventLoopGroup terminated successfully"); 431 } 432 } else { 433 if (logger.isLoggable(Level.FINE)) { 434 logger.logp(Level.FINE, cn, "<ChannelFuture listener>", "EventLoopGroup terminated with problems: {0}", f.cause()); 435 } 436 final Throwable throwable = f.cause(); 437 if (throwable != null) { 438 synchronized (this.shutdownProblems) { 439 this.shutdownProblems.add(throwable); 440 } 441 } 442 } 443 } finally { 444 if (logger.isLoggable(Level.FINE)) { 445 logger.logp(Level.FINE, cn, "<ChannelFuture listener>", "Counting down shutdownLatch"); 446 } 447 // See the waitForAllServersToStop() method 448 // below which calls await() on this latch. 449 this.shutdownLatch.countDown(); 450 } 451 }); 452 453 Collection<EventExecutorGroup> eventExecutorGroups = this.eventExecutorGroups; 454 if (eventExecutorGroups == null) { 455 eventExecutorGroups = new ArrayList<>(); 456 this.eventExecutorGroups = eventExecutorGroups; 457 } 458 synchronized (eventExecutorGroups) { 459 eventExecutorGroups.add(eventLoopGroup); 460 } 461 462 final Future<?> bindFuture; 463 if (Thread.currentThread().isInterrupted()) { 464 // Don't bother binding. 465 bindFuture = null; 466 final CountDownLatch bindLatch = this.bindLatch; 467 if (bindLatch != null) { 468 zeroOut(bindLatch); 469 this.bindLatch = null; 470 } 471 } else if (serverBootstrapConfig.localAddress() == null) { 472 bindFuture = serverBootstrap.bind(baseUri.getHost(), baseUri.getPort()); 473 } else { 474 bindFuture = serverBootstrap.bind(); 475 } 476 if (bindFuture != null) { 477 bindFuture.addListener(f -> { 478 try { 479 if (f.isSuccess()) { 480 if (logger.isLoggable(Level.FINE)) { 481 logger.logp(Level.FINE, cn, "<ChannelFuture listener>", "ServerBootstrap bound successfully"); 482 } 483 } else { 484 final Throwable throwable = f.cause(); 485 if (logger.isLoggable(Level.WARNING)) { 486 logger.logp(Level.WARNING, cn, "<ChannelFuture listener>", "ServerBootstrap binding failed: {0}", throwable); 487 } 488 if (throwable != null) { 489 synchronized (bindProblems) { 490 bindProblems.add(throwable); 491 } 492 } 493 } 494 } finally { 495 final CountDownLatch bindLatch = this.bindLatch; 496 if (bindLatch != null) { 497 if (logger.isLoggable(Level.FINE)) { 498 logger.logp(Level.FINE, cn, "<ChannelFuture listener>", "Counting down bindLatch"); 499 } 500 bindLatch.countDown(); 501 } 502 } 503 }); 504 } 505 506 } catch (final RuntimeException | URISyntaxException throwMe) { 507 zeroOut(this.bindLatch); 508 zeroOut(this.shutdownLatch); 509 synchronized (bindProblems) { 510 for (final Throwable bindProblem : bindProblems) { 511 throwMe.addSuppressed(bindProblem); 512 } 513 } 514 cc.release(); 515 throw throwMe; 516 } 517 518 } 519 520 CountDownLatch bindLatch = this.bindLatch; 521 if (!Thread.currentThread().isInterrupted() && bindLatch != null && bindLatch.getCount() > 0) { 522 try { 523 if (logger.isLoggable(Level.FINE)) { 524 logger.logp(Level.FINE, cn, mn, "Awaiting bindLatch"); 525 } 526 bindLatch.await(); 527 if (logger.isLoggable(Level.FINE)) { 528 logger.logp(Level.FINE, cn, mn, "bindLatch released"); 529 } 530 } catch (final InterruptedException interruptedException) { 531 if (logger.isLoggable(Level.FINE)) { 532 logger.logp(Level.FINE, cn, mn, "bindLatch.await() interrupted"); 533 } 534 Thread.currentThread().interrupt(); 535 zeroOut(bindLatch); 536 bindLatch = null; 537 this.bindLatch = null; 538 } 539 } 540 541 DeploymentException throwMe = null; 542 synchronized (bindProblems) { 543 for (final Throwable bindProblem : bindProblems) { 544 if (throwMe == null) { 545 throwMe = new DeploymentException(bindProblem); 546 } else { 547 throwMe.addSuppressed(bindProblem); 548 } 549 } 550 bindProblems.clear(); 551 } 552 if (throwMe != null) { 553 zeroOut(this.shutdownLatch); 554 cc.release(); 555 throw throwMe; 556 } 557 558 if (bindLatch != null) { 559 // If we get here with a non-null bindLatch, then a binding 560 // attempt was made and allowed to run to completion. The 561 // completion was successful, because otherwise we would have 562 // thrown an exception. 563 assert bindLatch.getCount() <= 0; 564 this.bindLatch = null; 565 566 // Since we know that binding happened and wasn't interrupted 567 // and didn't fail, we can now create the runLatch, which will 568 // be awaited in the waitForAllServersToStop() method below. 569 if (this.logger.isLoggable(Level.FINE)) { 570 this.logger.logp(Level.FINE, cn, mn, "Creating runLatch"); 571 } 572 this.runLatch = new CountDownLatch(1); 573 } 574 575 } 576 assert this.bindLatch == null; 577 578 if (this.logger.isLoggable(Level.FINER)) { 579 this.logger.exiting(cn, mn); 580 } 581 } 582 583 private final void waitForAllServersToStop(@Observes @BeforeDestroyed(ApplicationScoped.class) 584 final Object event) { 585 final String cn = this.getClass().getName(); 586 final String mn = "waitForAllServersToStop"; 587 if (this.logger.isLoggable(Level.FINER)) { 588 this.logger.entering(cn, mn, event); 589 } 590 591 // Note: Weld can fire a @BeforeDestroyed event on a thread that 592 // is not the container thread: a shutdown hook that it installs. 593 // So we have to take care to be thread safe. 594 595 CountDownLatch runLatch = this.runLatch; 596 if (runLatch != null) { 597 if (Thread.currentThread().isInterrupted()) { 598 zeroOut(runLatch); 599 } else { 600 try { 601 if (logger.isLoggable(Level.FINE)) { 602 logger.logp(Level.FINE, cn, mn, "Awaiting runLatch"); 603 } 604 runLatch.await(); 605 if (logger.isLoggable(Level.FINE)) { 606 logger.logp(Level.FINE, cn, mn, "runLatch released"); 607 } 608 } catch (final InterruptedException interruptedException) { 609 if (logger.isLoggable(Level.FINE)) { 610 logger.logp(Level.FINE, cn, mn, "runLatch.await() interrupted"); 611 } 612 Thread.currentThread().interrupt(); 613 if (logger.isLoggable(Level.FINE)) { 614 logger.logp(Level.FINE, cn, mn, "Zeroing out runLatch"); 615 } 616 zeroOut(runLatch); 617 } 618 } 619 assert runLatch.getCount() <= 0; 620 runLatch = null; 621 this.runLatch = null; 622 } 623 624 final Collection<? extends EventExecutorGroup> eventExecutorGroups = this.eventExecutorGroups; 625 if (eventExecutorGroups != null) { 626 synchronized (eventExecutorGroups) { 627 if (!eventExecutorGroups.isEmpty()) { 628 for (final EventExecutorGroup eventExecutorGroup : eventExecutorGroups) { 629 if (logger.isLoggable(Level.FINE)) { 630 logger.logp(Level.FINE, cn, mn, "Shutting down {0} gracefully", eventExecutorGroup); 631 } 632 // Sends a request to the Netty threads to wait for a 633 // configurable period of time and then shut down 634 // carefully. This call itself does not block. 635 eventExecutorGroup.shutdownGracefully(); 636 } 637 eventExecutorGroups.clear(); 638 } 639 } 640 this.eventExecutorGroups = null; 641 } 642 643 final CountDownLatch shutdownLatch = this.shutdownLatch; 644 if (shutdownLatch != null) { 645 try { 646 if (logger.isLoggable(Level.FINE)) { 647 logger.logp(Level.FINE, cn, mn, "Awaiting shutdownLatch"); 648 } 649 // Wait for the EventExecutorGroup ChannelFuture to complete 650 // and call shutdownLatch.countDown(). 651 shutdownLatch.await(); 652 if (logger.isLoggable(Level.FINE)) { 653 logger.logp(Level.FINE, cn, mn, "shutdownLatch released"); 654 } 655 } catch (final InterruptedException interruptedException) { 656 if (logger.isLoggable(Level.FINE)) { 657 logger.logp(Level.FINE, cn, mn, "shutdownLatch.await() interrupted"); 658 } 659 Thread.currentThread().interrupt(); 660 } 661 assert shutdownLatch.getCount() <= 0; 662 this.shutdownLatch = null; 663 } 664 665 final CreationalContext<?> cc = this.cc; 666 if (cc != null) { 667 cc.release(); 668 } 669 670 DeploymentException throwMe = null; 671 synchronized (this.shutdownProblems) { 672 for (final Throwable shutdownProblem : this.shutdownProblems) { 673 if (throwMe == null) { 674 throwMe = new DeploymentException(shutdownProblem); 675 } else { 676 throwMe.addSuppressed(shutdownProblem); 677 } 678 } 679 this.shutdownProblems.clear(); 680 } 681 if (throwMe != null) { 682 throw throwMe; 683 } 684 685 if (this.logger.isLoggable(Level.FINER)) { 686 this.logger.exiting(cn, mn); 687 } 688 } 689 690 691 /* 692 * Production and lookup methods. 693 */ 694 695 696 private static final SslContext getSslContext(final BeanManager beanManager, 697 final CreationalContext<?> cc, 698 final Annotation[] qualifiersArray) { 699 return acquire(beanManager, 700 cc, 701 SslContext.class, 702 qualifiersArray, 703 true, 704 (bm, cctx, qa) -> null); 705 } 706 707 private static final ServerBootstrap getServerBootstrap(final BeanManager beanManager, 708 final CreationalContext<?> cc, 709 final Annotation[] qualifiersArray) { 710 return acquire(beanManager, 711 cc, 712 ServerBootstrap.class, 713 qualifiersArray, 714 true, 715 (bm, cctx, qa) -> { 716 final ServerBootstrap returnValue = new ServerBootstrap(); 717 // See https://stackoverflow.com/a/28342821/208288 718 returnValue.group(getEventLoopGroup(bm, cctx, qa)); 719 returnValue.channelFactory(getChannelFactory(bm, cctx, qa)); 720 721 // Permit arbitrary customization 722 beanManager.getEvent().select(ServerBootstrap.class, qualifiersArray).fire(returnValue); 723 return returnValue; 724 }); 725 } 726 727 private static final ChannelFactory<? extends ServerChannel> getChannelFactory(final BeanManager beanManager, 728 final CreationalContext<?> cc, 729 final Annotation[] qualifiersArray) { 730 return acquire(beanManager, 731 cc, 732 new TypeLiteral<ChannelFactory<? extends ServerChannel>>() { 733 private static final long serialVersionUID = 1L; 734 }.getType(), 735 qualifiersArray, 736 true, 737 (bm, cctx, qa) -> { 738 final SelectorProvider selectorProvider = getSelectorProvider(bm, cctx, qa); 739 assert selectorProvider != null; 740 return () -> new NioServerSocketChannel(selectorProvider); 741 }); 742 } 743 744 private static final EventLoopGroup getEventLoopGroup(final BeanManager beanManager, 745 final CreationalContext<?> cc, 746 final Annotation[] qualifiersArray) { 747 return acquire(beanManager, 748 cc, 749 EventLoopGroup.class, 750 qualifiersArray, 751 true, 752 (bm, cctx, qa) -> { 753 final EventLoopGroup returnValue = 754 new NioEventLoopGroup(0 /* 0 == default number of threads */, 755 getExecutor(bm, cctx, qa), // null is OK 756 getEventExecutorChooserFactory(bm, cctx, qa), 757 getSelectorProvider(bm, cctx, qa), 758 getSelectStrategyFactory(bm, cctx, qa), 759 getRejectedExecutionHandler(bm, cctx, qa)); 760 // Permit arbitrary customization. (Not much you can do here 761 // except call setIoRatio(int).) 762 beanManager.getEvent().select(EventLoopGroup.class, qa).fire(returnValue); 763 return returnValue; 764 }); 765 } 766 767 private static final Executor getExecutor(final BeanManager beanManager, 768 final CreationalContext<?> cc, 769 final Annotation[] qualifiersArray) { 770 return acquire(beanManager, 771 cc, 772 Executor.class, 773 qualifiersArray, 774 false, // do not fall back to @Default one 775 (bm, cctx, qa) -> null); 776 } 777 778 private static final RejectedExecutionHandler getRejectedExecutionHandler(final BeanManager beanManager, 779 final CreationalContext<?> cc, 780 final Annotation[] qualifiersArray) { 781 return acquire(beanManager, 782 cc, 783 RejectedExecutionHandler.class, 784 qualifiersArray, 785 true, 786 (bm, cctx, qa) -> RejectedExecutionHandlers.reject()); 787 } 788 789 private static final SelectorProvider getSelectorProvider(final BeanManager beanManager, 790 final CreationalContext<?> cc, 791 final Annotation[] qualifiersArray) { 792 return acquire(beanManager, 793 cc, 794 SelectorProvider.class, 795 qualifiersArray, 796 true, 797 (bm, cctx, qa) -> SelectorProvider.provider()); 798 } 799 800 private static final SelectStrategyFactory getSelectStrategyFactory(final BeanManager beanManager, 801 final CreationalContext<?> cc, 802 final Annotation[] qualifiersArray) { 803 return acquire(beanManager, 804 cc, 805 SelectStrategyFactory.class, 806 qualifiersArray, 807 true, 808 (bm, cctx, qa) -> DefaultSelectStrategyFactory.INSTANCE); 809 } 810 811 private static final EventExecutorChooserFactory getEventExecutorChooserFactory(final BeanManager beanManager, 812 final CreationalContext<?> cc, 813 final Annotation[] qualifiersArray) { 814 return acquire(beanManager, 815 cc, 816 EventExecutorChooserFactory.class, 817 qualifiersArray, 818 true, 819 (bm, cctx, qa) -> DefaultEventExecutorChooserFactory.INSTANCE); 820 } 821 822 823 /* 824 * Static utility methods. 825 */ 826 827 828 private static final <T> T acquire(final BeanManager beanManager, 829 final CreationalContext<?> cc, 830 final Type type) { 831 return acquire(beanManager, cc, type, null, false, (bm, cctx, qa) -> null); 832 } 833 834 private static final <T> T acquire(final BeanManager beanManager, 835 final CreationalContext<?> cc, 836 final Type type, 837 final Annotation[] qualifiersArray, 838 final boolean fallbackWithDefaultQualifier, 839 final DefaultValueFunction<? extends T> defaultValueFunction) { 840 Objects.requireNonNull(beanManager); 841 Objects.requireNonNull(type); 842 Objects.requireNonNull(defaultValueFunction); 843 844 final T returnValue; 845 846 Set<Bean<?>> beans = null; 847 if (qualifiersArray == null || qualifiersArray.length <= 0 || (qualifiersArray.length == 1 && qualifiersArray[0] instanceof Default)) { 848 beans = beanManager.getBeans(type); 849 } else { 850 beans = beanManager.getBeans(type, qualifiersArray); 851 if (fallbackWithDefaultQualifier && (beans == null || beans.isEmpty())) { 852 beans = beanManager.getBeans(type); 853 } 854 } 855 if (beans == null || beans.isEmpty()) { 856 returnValue = defaultValueFunction.getDefaultValue(beanManager, cc, qualifiersArray); 857 } else { 858 final Bean<?> bean = beanManager.resolve(beans); 859 if (bean == null) { 860 returnValue = defaultValueFunction.getDefaultValue(beanManager, cc, qualifiersArray); 861 } else { 862 @SuppressWarnings("unchecked") 863 final T temp = (T)beanManager.getReference(bean, type, cc); 864 returnValue = temp; 865 } 866 } 867 868 return returnValue; 869 } 870 871 private static final void zeroOut(final CountDownLatch latch) { 872 if (latch != null) { 873 while (latch.getCount() > 0L) { 874 latch.countDown(); 875 } 876 assert latch.getCount() == 0L; 877 } 878 } 879 880 private static final Map<String, String> toConfigurationCoordinates(final Set<? extends Annotation> qualifiers) { 881 final Map<String, String> returnValue = new HashMap<>(); 882 if (qualifiers != null && !qualifiers.isEmpty()) { 883 for (final Annotation qualifier : qualifiers) { 884 if (qualifier instanceof Named) { 885 returnValue.put("name", ((Named)qualifier).value()); 886 } else if (!(qualifier instanceof Default) && !(qualifier instanceof Any)) { 887 returnValue.put(qualifier.toString(), ""); 888 } 889 } 890 } 891 return returnValue; 892 } 893 894 895 /* 896 * Inner and nested classes. 897 */ 898 899 900 @FunctionalInterface 901 private static interface DefaultValueFunction<T> { 902 903 T getDefaultValue(final BeanManager beanManager, 904 final CreationalContext<?> cc, 905 final Annotation[] qualifiersArray); 906 907 } 908 909 private static final class NoOpHandler extends Handler { 910 911 private final Thread thread; 912 913 private NoOpHandler(final Thread t) { 914 super(); 915 this.thread = Objects.requireNonNull(t); 916 } 917 918 @Override 919 public final void close() { 920 try { 921 this.thread.join(); 922 } catch (final InterruptedException interruptedException) { 923 Thread.currentThread().interrupt(); 924 } 925 } 926 927 @Override 928 public final void flush() { 929 930 } 931 932 @Override 933 public final void publish(final LogRecord logRecord) { 934 935 } 936 937 } 938 939}