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}