/**
   * Check to see if the ServletContainerIntializer loaded via the ServiceLoader came from a jar
   * that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
   *
   * @param context
   * @param sci
   * @return true if excluded
   */
  public boolean isFromExcludedJar(
      WebAppContext context, ServletContainerInitializer sci, Resource sciResource)
      throws Exception {
    if (sci == null) throw new IllegalArgumentException("ServletContainerInitializer null");
    if (context == null) throw new IllegalArgumentException("WebAppContext null");

    if (LOG.isDebugEnabled()) LOG.debug("Checking {} for jar exclusion", sci);

    // A ServletContainerInitializer that came from the container's classpath cannot be excluded by
    // an ordering
    // of WEB-INF/lib jars
    if (isFromContainerClassPath(context, sci)) return false;

    List<Resource> orderedJars = context.getMetaData().getOrderedWebInfJars();

    // If no ordering, nothing is excluded
    if (context.getMetaData().getOrdering() == null) return false;

    // there is an ordering, but there are no jars resulting from the ordering, everything excluded
    if (orderedJars.isEmpty()) return true;

    if (sciResource == null)
      return false; // not from a jar therefore not from WEB-INF so not excludable

    URI loadingJarURI = sciResource.getURI();
    boolean found = false;
    Iterator<Resource> itor = orderedJars.iterator();
    while (!found && itor.hasNext()) {
      Resource r = itor.next();
      found = r.getURI().equals(loadingJarURI);
    }

    return !found;
  }
  /**
   * Scan jars in WEB-INF/lib
   *
   * @param context
   * @param parser
   * @throws Exception
   */
  public void parseWebInfLib(final WebAppContext context, final AnnotationParser parser)
      throws Exception {
    List<FragmentDescriptor> frags = context.getMetaData().getFragments();

    // email from Rajiv Mordani jsrs 315 7 April 2010
    // jars that do not have a web-fragment.xml are still considered fragments
    // they have to participate in the ordering
    ArrayList<URI> webInfUris = new ArrayList<URI>();

    List<Resource> jars = context.getMetaData().getOrderedWebInfJars();

    // No ordering just use the jars in any order
    if (jars == null || jars.isEmpty()) jars = context.getMetaData().getWebInfJars();

    _webInfLibStats = new CounterStatistic();

    for (Resource r : jars) {
      // for each jar, we decide which set of annotations we need to parse for
      final Set<Handler> handlers = new HashSet<Handler>();

      FragmentDescriptor f = getFragmentFromJar(r, frags);

      // if its from a fragment jar that is metadata complete, we should skip scanning for
      // @webservlet etc
      // but yet we still need to do the scanning for the classes on behalf of  the
      // servletcontainerinitializers
      // if a jar has no web-fragment.xml we scan it (because it is not excluded by the ordering)
      // or if it has a fragment we scan it if it is not metadata complete
      if (f == null
          || !isMetaDataComplete(f)
          || _classInheritanceHandler != null
          || !_containerInitializerAnnotationHandlers.isEmpty()) {
        // register the classinheritance handler if there is one
        if (_classInheritanceHandler != null) handlers.add(_classInheritanceHandler);

        // register the handlers for the @HandlesTypes values that are themselves annotations if
        // there are any
        handlers.addAll(_containerInitializerAnnotationHandlers);

        // only register the discoverable annotation handlers if this fragment is not metadata
        // complete, or has no fragment descriptor
        if (f == null || !isMetaDataComplete(f)) handlers.addAll(_discoverableAnnotationHandlers);

        if (_parserTasks != null) {
          ParserTask task = new ParserTask(parser, handlers, r, _webAppClassNameResolver);
          _parserTasks.add(task);
          _webInfLibStats.increment();
          if (LOG.isDebugEnabled()) task.setStatistic(new TimeStatistic());
        }
      }
    }
  }
  /**
   * Here is the order in which jars and osgi artifacts are scanned for discoverable annotations.
   *
   * <ol>
   *   <li>The container jars are scanned.
   *   <li>The WEB-INF/classes are scanned
   *   <li>The osgi fragment to the web bundle are parsed.
   *   <li>The WEB-INF/lib are scanned
   *   <li>The required bundles are parsed
   * </ol>
   */
  @Override
  public void parseWebInfLib(
      WebAppContext context, org.eclipse.jetty.annotations.AnnotationParser parser)
      throws Exception {
    AnnotationParser oparser = (AnnotationParser) parser;

    Bundle webbundle = (Bundle) context.getAttribute(OSGiWebappConstants.JETTY_OSGI_BUNDLE);
    Set<Bundle> fragAndRequiredBundles =
        (Set<Bundle>) context.getAttribute(OSGiWebInfConfiguration.FRAGMENT_AND_REQUIRED_BUNDLES);
    if (fragAndRequiredBundles != null) {
      // index and scan fragments
      for (Bundle bundle : fragAndRequiredBundles) {
        // skip bundles that have been uninstalled since we discovered them
        if (bundle.getState() == Bundle.UNINSTALLED) continue;

        Resource bundleRes = oparser.indexBundle(bundle);
        if (!context.getMetaData().getWebInfJars().contains(bundleRes)) {
          context.getMetaData().addWebInfJar(bundleRes);
        }

        if (bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null) {
          // a fragment indeed:
          parseFragmentBundle(context, oparser, webbundle, bundle);
        }
      }
    }
    // scan ourselves
    oparser.indexBundle(webbundle);
    parseWebBundle(context, oparser, webbundle);

    // scan the WEB-INF/lib
    super.parseWebInfLib(context, parser);
    if (fragAndRequiredBundles != null) {
      // scan the required bundles
      for (Bundle requiredBundle : fragAndRequiredBundles) {
        // skip bundles that have been uninstalled since we discovered them
        if (requiredBundle.getState() == Bundle.UNINSTALLED) continue;

        if (requiredBundle.getHeaders().get(Constants.FRAGMENT_HOST) == null) {
          // a bundle indeed:
          parseRequiredBundle(context, oparser, webbundle, requiredBundle);
        }
      }
    }
  }
  /**
   * Scan classes in WEB-INF/classes
   *
   * @param context
   * @param parser
   * @throws Exception
   */
  public void parseWebInfClasses(final WebAppContext context, final AnnotationParser parser)
      throws Exception {
    Set<Handler> handlers = new HashSet<Handler>();
    handlers.addAll(_discoverableAnnotationHandlers);
    if (_classInheritanceHandler != null) handlers.add(_classInheritanceHandler);
    handlers.addAll(_containerInitializerAnnotationHandlers);

    _webInfClassesStats = new CounterStatistic();

    for (Resource dir : context.getMetaData().getWebInfClassesDirs()) {
      if (_parserTasks != null) {
        ParserTask task = new ParserTask(parser, handlers, dir, _webAppClassNameResolver);
        _parserTasks.add(task);
        _webInfClassesStats.increment();
        if (LOG.isDebugEnabled()) task.setStatistic(new TimeStatistic());
      }
    }
  }
  public void doHandle(Class clazz) {
    // Check that the PreDestroy is on a class that we're interested in
    if (Util.supportsPostConstructPreDestroy(clazz)) {
      Method[] methods = clazz.getDeclaredMethods();
      for (int i = 0; i < methods.length; i++) {
        Method m = (Method) methods[i];
        if (m.isAnnotationPresent(PreDestroy.class)) {
          if (m.getParameterTypes().length != 0)
            throw new IllegalStateException(m + " has parameters");
          if (m.getReturnType() != Void.TYPE) throw new IllegalStateException(m + " is not void");
          if (m.getExceptionTypes().length != 0)
            throw new IllegalStateException(m + " throws checked exceptions");
          if (Modifier.isStatic(m.getModifiers()))
            throw new IllegalStateException(m + " is static");

          // ServletSpec 3.0 p80 If web.xml declares even one predestroy then all predestroys
          // in fragments must be ignored. Otherwise, they are additive.
          MetaData metaData = _context.getMetaData();
          Origin origin = metaData.getOrigin("pre-destroy");
          if (origin != null
              && (origin == Origin.WebXml
                  || origin == Origin.WebDefaults
                  || origin == Origin.WebOverride)) return;

          PreDestroyCallback callback = new PreDestroyCallback();
          callback.setTarget(clazz.getName(), m.getName());

          LifeCycleCallbackCollection lifecycles =
              (LifeCycleCallbackCollection)
                  _context.getAttribute(LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION);
          if (lifecycles == null) {
            lifecycles = new LifeCycleCallbackCollection();
            _context.setAttribute(
                LifeCycleCallbackCollection.LIFECYCLE_CALLBACK_COLLECTION, lifecycles);
          }

          lifecycles.add(callback);
        }
      }
    }
  }
  /**
   * Scan jars on container path.
   *
   * @param context
   * @param parser
   * @throws Exception
   */
  public void parseContainerPath(final WebAppContext context, final AnnotationParser parser)
      throws Exception {
    // always parse for discoverable annotations as well as class hierarchy and
    // servletcontainerinitializer related annotations
    final Set<Handler> handlers = new HashSet<Handler>();
    handlers.addAll(_discoverableAnnotationHandlers);
    handlers.addAll(_containerInitializerAnnotationHandlers);
    if (_classInheritanceHandler != null) handlers.add(_classInheritanceHandler);

    _containerPathStats = new CounterStatistic();

    for (Resource r : context.getMetaData().getContainerResources()) {
      // queue it up for scanning if using multithreaded mode
      if (_parserTasks != null) {
        ParserTask task = new ParserTask(parser, handlers, r, _containerClassNameResolver);
        _parserTasks.add(task);
        _containerPathStats.increment();
        if (LOG.isDebugEnabled()) task.setStatistic(new TimeStatistic());
      }
    }
  }
  /**
   * @see
   *     org.eclipse.jetty.webapp.AbstractConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
   */
  @Override
  public void configure(WebAppContext context) throws Exception {
    context.addDecorator(new AnnotationDecorator(context));

    // Even if metadata is complete, we still need to scan for ServletContainerInitializers - if
    // there are any

    if (!context.getMetaData().isMetaDataComplete()) {
      // If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we
      // need to search for annotations
      if (context.getServletContext().getEffectiveMajorVersion() >= 3
          || context.isConfigurationDiscovered()) {
        _discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context));
        _discoverableAnnotationHandlers.add(new WebFilterAnnotationHandler(context));
        _discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context));
      }
    }

    // Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes,
    // then we need to scan all the
    // classes so we can call their onStartup() methods correctly
    createServletContainerInitializerAnnotationHandlers(
        context, getNonExcludedInitializers(context));

    if (!_discoverableAnnotationHandlers.isEmpty()
        || _classInheritanceHandler != null
        || !_containerInitializerAnnotationHandlers.isEmpty()) scanForAnnotations(context);

    // Resolve container initializers
    List<ContainerInitializer> initializers =
        (List<ContainerInitializer>)
            context.getAttribute(AnnotationConfiguration.CONTAINER_INITIALIZERS);
    if (initializers != null && initializers.size() > 0) {
      Map<String, Set<String>> map =
          (Map<String, Set<String>>)
              context.getAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP);
      if (map == null) LOG.warn("ServletContainerInitializers: detected. Class hierarchy: empty");
      for (ContainerInitializer i : initializers) i.resolveClasses(context, map);
    }
  }
  /**
   * @param context
   * @return list of non-excluded {@link ServletContainerInitializer}s
   * @throws Exception
   */
  public List<ServletContainerInitializer> getNonExcludedInitializers(WebAppContext context)
      throws Exception {
    ArrayList<ServletContainerInitializer> nonExcludedInitializers =
        new ArrayList<ServletContainerInitializer>();

    // We use the ServiceLoader mechanism to find the ServletContainerInitializer classes to inspect
    long start = 0;

    ClassLoader old = Thread.currentThread().getContextClassLoader();
    ServiceLoader<ServletContainerInitializer> loadedInitializers = null;
    try {
      if (LOG.isDebugEnabled()) start = System.nanoTime();
      Thread.currentThread().setContextClassLoader(context.getClassLoader());
      loadedInitializers = ServiceLoader.load(ServletContainerInitializer.class);
    } finally {
      Thread.currentThread().setContextClassLoader(old);
    }

    if (LOG.isDebugEnabled())
      LOG.debug(
          "Service loaders found in {}ms",
          (TimeUnit.MILLISECONDS.convert((System.nanoTime() - start), TimeUnit.NANOSECONDS)));

    Map<ServletContainerInitializer, Resource> sciResourceMap =
        new HashMap<ServletContainerInitializer, Resource>();
    ServletContainerInitializerOrdering initializerOrdering = getInitializerOrdering(context);

    // Get initial set of SCIs that aren't from excluded jars or excluded by the
    // containerExclusionPattern, or excluded
    // because containerInitializerOrdering omits it
    for (ServletContainerInitializer sci : loadedInitializers) {
      if (matchesExclusionPattern(sci)) {
        if (LOG.isDebugEnabled()) LOG.debug("{} excluded by pattern", sci);
        continue;
      }

      Resource sciResource = getJarFor(sci);
      if (isFromExcludedJar(context, sci, sciResource)) {
        if (LOG.isDebugEnabled()) LOG.debug("{} is from excluded jar", sci);
        continue;
      }

      // check containerInitializerOrdering doesn't exclude it
      String name = sci.getClass().getName();
      if (initializerOrdering != null
          && (!initializerOrdering.hasWildcard() && initializerOrdering.getIndexOf(name) < 0)) {
        if (LOG.isDebugEnabled()) LOG.debug("{} is excluded by ordering", sci);
        continue;
      }

      sciResourceMap.put(sci, sciResource);
    }

    // Order the SCIs that are included
    if (initializerOrdering != null && !initializerOrdering.isDefaultOrder()) {
      if (LOG.isDebugEnabled())
        LOG.debug("Ordering ServletContainerInitializers with " + initializerOrdering);

      // There is an ordering that is not just "*".
      // Arrange ServletContainerInitializers according to the ordering of classnames given,
      // irrespective of coming from container or webapp classpaths
      nonExcludedInitializers.addAll(sciResourceMap.keySet());
      Collections.sort(
          nonExcludedInitializers, new ServletContainerInitializerComparator(initializerOrdering));
    } else {
      // No jetty-specific ordering specified, or just the wildcard value "*" specified.
      // Fallback to ordering the ServletContainerInitializers according to:
      // container classpath first, WEB-INF/classes then WEB-INF/lib (obeying any web.xml jar
      // ordering)

      // no web.xml ordering defined, add SCIs in any order
      if (context.getMetaData().getOrdering() == null) {
        if (LOG.isDebugEnabled())
          LOG.debug("No web.xml ordering, ServletContainerInitializers in random order");
        nonExcludedInitializers.addAll(sciResourceMap.keySet());
      } else {
        if (LOG.isDebugEnabled())
          LOG.debug(
              "Ordering ServletContainerInitializers with ordering {}",
              context.getMetaData().getOrdering());
        for (Map.Entry<ServletContainerInitializer, Resource> entry : sciResourceMap.entrySet()) {
          // add in SCIs from the container classpath
          if (entry.getKey().getClass().getClassLoader() == context.getClassLoader().getParent())
            nonExcludedInitializers.add(entry.getKey());
          else if (entry.getValue()
              == null) // add in SCIs not in a jar, as they must be from WEB-INF/classes and can't
                       // be ordered
          nonExcludedInitializers.add(entry.getKey());
        }

        // add SCIs according to the ordering of its containing jar
        for (Resource webInfJar : context.getMetaData().getOrderedWebInfJars()) {
          for (Map.Entry<ServletContainerInitializer, Resource> entry : sciResourceMap.entrySet()) {
            if (webInfJar.equals(entry.getValue())) nonExcludedInitializers.add(entry.getKey());
          }
        }
      }
    }

    if (LOG.isDebugEnabled()) {
      int i = 0;
      for (ServletContainerInitializer sci : nonExcludedInitializers)
        LOG.debug("ServletContainerInitializer: {} {}", (++i), sci.getClass().getName());
    }
    return nonExcludedInitializers;
  }
  /**
   * Perform scanning of classes for annotations
   *
   * @param context
   * @throws Exception
   */
  protected void scanForAnnotations(WebAppContext context) throws Exception {
    AnnotationParser parser = createAnnotationParser();
    _parserTasks = new ArrayList<ParserTask>();

    long start = 0;

    if (LOG.isDebugEnabled())
      LOG.debug(
          "Annotation scanning commencing: webxml={}, metadatacomplete={}, configurationDiscovered={}, multiThreaded={}, maxScanWait={}",
          context.getServletContext().getEffectiveMajorVersion(),
          context.getMetaData().isMetaDataComplete(),
          context.isConfigurationDiscovered(),
          isUseMultiThreading(context),
          getMaxScanWait(context));

    parseContainerPath(context, parser);
    // email from Rajiv Mordani jsrs 315 7 April 2010
    //    If there is a <others/> then the ordering should be
    //          WEB-INF/classes the order of the declared elements + others.
    //    In case there is no others then it is
    //          WEB-INF/classes + order of the elements.
    parseWebInfClasses(context, parser);
    parseWebInfLib(context, parser);

    start = System.nanoTime();

    // execute scan, either effectively synchronously (1 thread only), or asynchronously (limited by
    // number of processors available)
    final Semaphore task_limit =
        (isUseMultiThreading(context)
            ? new Semaphore(Runtime.getRuntime().availableProcessors())
            : new Semaphore(1));
    final CountDownLatch latch = new CountDownLatch(_parserTasks.size());
    final MultiException me = new MultiException();

    for (final ParserTask p : _parserTasks) {
      task_limit.acquire();
      context
          .getServer()
          .getThreadPool()
          .execute(
              new Runnable() {
                @Override
                public void run() {
                  try {
                    p.call();
                  } catch (Exception e) {
                    me.add(e);
                  } finally {
                    task_limit.release();
                    latch.countDown();
                  }
                }
              });
    }

    boolean timeout = !latch.await(getMaxScanWait(context), TimeUnit.SECONDS);

    if (LOG.isDebugEnabled()) {
      for (ParserTask p : _parserTasks)
        LOG.debug(
            "Scanned {} in {}ms",
            p.getResource(),
            TimeUnit.MILLISECONDS.convert(p.getStatistic().getElapsed(), TimeUnit.NANOSECONDS));

      LOG.debug(
          "Scanned {} container path jars, {} WEB-INF/lib jars, {} WEB-INF/classes dirs in {}ms for context {}",
          _containerPathStats.getTotal(),
          _webInfLibStats.getTotal(),
          _webInfClassesStats.getTotal(),
          (TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS)),
          context);
    }

    if (timeout) me.add(new Exception("Timeout scanning annotations"));
    me.ifExceptionThrow();
  }