@SuppressWarnings({"UnusedDeclaration"})
public class OpenEJBNamingContextListener implements LifecycleListener, PropertyChangeListener {
  private static final Logger logger =
      Logger.getInstance(
          LogCategory.OPENEJB.createChild("tomcat"), "org.apache.openejb.util.resources");

  /** Associated standardServer. */
  private final StandardServer standardServer;

  /** Has the listener been started? */
  private boolean running = false;

  /** Associated naming resources. */
  private final NamingResources namingResources;

  public OpenEJBNamingContextListener(StandardServer standardServer) {
    this.standardServer = standardServer;
    namingResources = standardServer.getGlobalNamingResources();
  }

  public void lifecycleEvent(LifecycleEvent event) {
    if (event.getLifecycle() != standardServer) {
      return;
    }

    if (Lifecycle.START_EVENT.equals(event.getType())) {
      start();

    } else if (Lifecycle.STOP_EVENT.equals(event.getType())) {

      stop();
    }
  }

  public boolean isRunning() {
    return running;
  }

  public void start() {
    if (running) return;

    namingResources.addPropertyChangeListener(this);
    processInitialNamingResources();

    running = true;
  }

  public void stop() {
    if (!running) return;

    namingResources.removePropertyChangeListener(this);

    running = false;
  }

  public void propertyChange(PropertyChangeEvent event) {
    if (!running) return;

    Object source = event.getSource();
    if (source == namingResources) {
      processGlobalResourcesChange(
          event.getPropertyName(), event.getOldValue(), event.getNewValue());
    }
  }

  /**
   * Process a property change on the global naming resources, by making the corresponding addition
   * or removal to OpenEJB.
   *
   * @param name Property name of the change to be processed
   * @param oldValue The old value (or <code>null</code> if adding)
   * @param newValue The new value (or <code>null</code> if removing)
   */
  private void processGlobalResourcesChange(String name, Object oldValue, Object newValue) {

    // NOTE - It seems that the Context for global JNDI resources
    // is left in read-write mode, so we do not have to change it here

    if (name.equals("ejb")) {
      if (oldValue != null) {
        ContextEjb ejb = (ContextEjb) oldValue;
        if (ejb.getName() != null) {
          removeEjb(ejb.getName());
        }
      }
      if (newValue != null) {
        ContextEjb ejb = (ContextEjb) newValue;
        if (ejb.getName() != null) {
          addEjb(ejb);
        }
      }
    } else if (name.equals("environment")) {
      if (oldValue != null) {
        ContextEnvironment env = (ContextEnvironment) oldValue;
        if (env.getName() != null) {
          removeEnvironment(env.getName());
        }
      }
      if (newValue != null) {
        ContextEnvironment env = (ContextEnvironment) newValue;
        if (env.getName() != null) {
          addEnvironment(env);
        }
      }
    } else if (name.equals("localEjb")) {
      if (oldValue != null) {
        ContextLocalEjb ejb = (ContextLocalEjb) oldValue;
        if (ejb.getName() != null) {
          removeLocalEjb(ejb.getName());
        }
      }
      if (newValue != null) {
        ContextLocalEjb ejb = (ContextLocalEjb) newValue;
        if (ejb.getName() != null) {
          addLocalEjb(ejb);
        }
      }
    } else if (name.equals("resource")) {
      if (oldValue != null) {
        ContextResource resource = (ContextResource) oldValue;
        if (resource.getName() != null) {
          removeResource(resource.getName());
        }
      }
      if (newValue != null) {
        ContextResource resource = (ContextResource) newValue;
        if (resource.getName() != null) {
          addResource(resource);
        }
      }
    } else if (name.equals("resourceEnvRef")) {
      if (oldValue != null) {
        ContextResourceEnvRef resourceEnvRef = (ContextResourceEnvRef) oldValue;
        if (resourceEnvRef.getName() != null) {
          removeResourceEnvRef(resourceEnvRef.getName());
        }
      }
      if (newValue != null) {
        ContextResourceEnvRef resourceEnvRef = (ContextResourceEnvRef) newValue;
        if (resourceEnvRef.getName() != null) {
          addResourceEnvRef(resourceEnvRef);
        }
      }
    } else if (name.equals("resourceLink")) {
      if (oldValue != null) {
        ContextResourceLink rl = (ContextResourceLink) oldValue;
        if (rl.getName() != null) {
          removeResourceLink(rl.getName());
        }
      }
      if (newValue != null) {
        ContextResourceLink rl = (ContextResourceLink) newValue;
        if (rl.getName() != null) {
          addResourceLink(rl);
        }
      }
    }
  }

  private void processInitialNamingResources() {
    // Resource links
    ContextResourceLink[] resourceLinks = namingResources.findResourceLinks();
    for (ContextResourceLink resourceLink : resourceLinks) {
      addResourceLink(resourceLink);
    }

    // Resources
    ContextResource[] resources = namingResources.findResources();
    for (ContextResource resource : resources) {
      addResource(resource);
    }

    // Resources Env
    ContextResourceEnvRef[] resourceEnvRefs = namingResources.findResourceEnvRefs();
    for (ContextResourceEnvRef resourceEnvRef : resourceEnvRefs) {
      addResourceEnvRef(resourceEnvRef);
    }

    // Environment entries
    ContextEnvironment[] contextEnvironments = namingResources.findEnvironments();
    for (ContextEnvironment contextEnvironment : contextEnvironments) {
      addEnvironment(contextEnvironment);
    }

    // EJB references
    ContextEjb[] ejbs = namingResources.findEjbs();
    for (ContextEjb ejb : ejbs) {
      addEjb(ejb);
    }
  }

  public void addEjb(ContextEjb ejb) {}

  public void addEnvironment(ContextEnvironment env) {
    bindResource(env);
  }

  public void addLocalEjb(ContextLocalEjb localEjb) {}

  public void addResource(ContextResource resource) {
    bindResource(resource);
  }

  public void addResourceEnvRef(ContextResourceEnvRef resourceEnvRef) {
    bindResource(resourceEnvRef);
  }

  private void bindResource(ResourceBase res) {
    try {
      Context globalNamingContext = standardServer.getGlobalNamingContext();
      Object value = globalNamingContext.lookup(res.getName());
      String type = res.getType();
      bindResource(res.getName(), value, type);
    } catch (NamingException e) {
      logger.error("Unable to lookup Global Tomcat resource " + res.getName(), e);
    }
  }

  private void bindResource(final String name, final Object value, final String type) {
    Assembler assembler =
        (Assembler) SystemInstance.get().getComponent(org.apache.openejb.spi.Assembler.class);
    try {
      assembler
          .getContainerSystem()
          .getJNDIContext()
          .lookup(Assembler.OPENEJB_RESOURCE_JNDI_PREFIX + name);
      return;
    } catch (NamingException ne) {
      // no-op: OK
    }

    final ResourceInfo resourceInfo = new ResourceInfo();
    resourceInfo.id = name;
    resourceInfo.service = "Resource";
    resourceInfo.types.add(type);
    PassthroughFactory.add(resourceInfo, value);

    logger.info(
        "Importing a Tomcat Resource with id '" + resourceInfo.id + "' of type '" + type + "'.");
    try {
      assembler.createResource(resourceInfo);
    } catch (OpenEJBException e) {
      logger.error("Unable to bind Global Tomcat resource " + name + " into OpenEJB", e);
    }
  }

  public void addResourceLink(ContextResourceLink resourceLink) {}

  public void removeEjb(String name) {}

  public void removeEnvironment(String name) {}

  public void removeLocalEjb(String name) {}

  public void removeResource(String name) {
    // there isn't any way to remove a resource yet
  }

  public void removeResourceEnvRef(String name) {
    // there isn't any way to remove a resource yet
  }

  public void removeResourceLink(String name) {}
}
/** @version $Rev$ $Date$ */
public class OptimizedLoaderService implements LoaderService {

  private static final Logger log =
      Logger.getInstance(LogCategory.OPENEJB.createChild("cdi"), OptimizedLoaderService.class);

  public static final ThreadLocal<Collection<String>> ADDITIONAL_EXTENSIONS =
      new ThreadLocal<Collection<String>>();

  private final LoaderService loaderService;

  public OptimizedLoaderService() {
    this(new DefaultLoaderService());
  }

  public OptimizedLoaderService(final LoaderService loaderService) {
    this.loaderService = loaderService;
  }

  @Override
  public <T> List<T> load(final Class<T> serviceType) {
    return load(serviceType, Thread.currentThread().getContextClassLoader());
  }

  @Override
  public <T> List<T> load(final Class<T> serviceType, final ClassLoader classLoader) {
    // ServiceLoader is expensive (can take up to a half second).  This is an optimization
    if (OpenWebBeansPlugin.class.equals(serviceType)) {
      return (List<T>) loadWebBeansPlugins(classLoader);
    }

    // As far as we know, this only is reached for CDI Extension discovery
    if (Extension.class.equals(serviceType)) {
      return (List<T>) loadExtensions(classLoader);
    }
    return loaderService.load(serviceType, classLoader);
  }

  protected List<? extends Extension> loadExtensions(final ClassLoader classLoader) {
    final List<Extension> list = loaderService.load(Extension.class, classLoader);
    final Collection<String> additional = ADDITIONAL_EXTENSIONS.get();
    if (additional != null) {
      for (final String name : additional) {
        try {
          list.add(Extension.class.cast(classLoader.loadClass(name).newInstance()));
        } catch (final Exception ignored) {
          // no-op
        }
      }
    }

    final Collection<Extension> extensionCopy = new ArrayList<>(list);

    final Iterator<Extension> it = list.iterator();
    while (it.hasNext()) {
      if (it.hasNext()) {
        if (isFiltered(extensionCopy, it.next())) {
          it.remove();
        }
      }
    }
    return list;
  }

  // mainly intended to avoid conflicts between internal and overrided spec extensions
  private boolean isFiltered(final Collection<Extension> extensions, final Extension next) {
    final ClassLoader containerLoader = ParentClassLoaderFinder.Helper.get();
    final Class<? extends Extension> extClass = next.getClass();
    if (extClass.getClassLoader() != containerLoader) {
      return false;
    }

    final String name = extClass.getName();
    switch (name) {
      case "org.apache.bval.cdi.BValExtension":
        for (final Extension e : extensions) {
          final String en = e.getClass().getName();

          // org.hibernate.validator.internal.cdi.ValidationExtension but allowing few evolutions of
          // packages
          if (en.startsWith("org.hibernate.validator.") && en.endsWith("ValidationExtension")) {
            log.info("Skipping BVal CDI integration cause hibernate was found in the application");
            return true;
          }
        }
        break;
      case "org.apache.batchee.container.cdi.BatchCDIInjectionExtension": // see
                                                                          // org.apache.openejb.batchee.BatchEEServiceManager
        return "true"
            .equals(SystemInstance.get().getProperty("tomee.batchee.cdi.use-extension", "false"));
      case "org.apache.commons.jcs.jcache.cdi.MakeJCacheCDIInterceptorFriendly":
        final String spi = "META-INF/services/javax.cache.spi.CachingProvider";
        try {
          final Enumeration<URL> appResources =
              Thread.currentThread().getContextClassLoader().getResources(spi);
          if (appResources != null && appResources.hasMoreElements()) {
            final Collection<URL> containerResources =
                Collections.list(containerLoader.getResources(spi));
            do {
              if (!containerResources.contains(appResources.nextElement())) {
                log.info(
                    "Skipping JCS CDI integration cause another provide was found in the application");
                return true;
              }
            } while (appResources.hasMoreElements());
          }
        } catch (final Exception e) {
          // no-op
        }
        break;
      default:
    }
    return false;
  }

  private List<? extends OpenWebBeansPlugin> loadWebBeansPlugins(final ClassLoader loader) {
    final List<OpenWebBeansPlugin> list = new ArrayList<>(2);
    list.add(new CdiPlugin());
    {
      final Class<?> clazz;
      try {
        clazz = loader.loadClass("org.apache.geronimo.openejb.cdi.GeronimoWebBeansPlugin");
        try {
          list.add(OpenWebBeansPlugin.class.cast(clazz.newInstance()));
        } catch (final Exception e) {
          log.error("Unable to load OpenWebBeansPlugin: GeronimoWebBeansPlugin");
        }
      } catch (final ClassNotFoundException e) {
        // ignore
      }
    }
    {
      final Class<?> clazz;
      try {
        clazz = loader.loadClass("org.apache.webbeans.jsf.plugin.OpenWebBeansJsfPlugin");
        try {
          list.add(
              OpenWebBeansPlugin.class.cast(
                  Proxy.newProxyInstance(
                      loader,
                      new Class<?>[] {OpenWebBeansPlugin.class},
                      new ClassLoaderAwareHandler(
                          clazz.getSimpleName(), clazz.newInstance(), loader))));
        } catch (final Exception e) {
          log.error("Unable to load OpenWebBeansPlugin: OpenWebBeansJsfPlugin");
        }
      } catch (final ClassNotFoundException e) {
        // ignore
      }
    }
    return list;
  }
}