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