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