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}