BinderRegisterExtension.java

/*
 * Copyright (c) 2021, 2024 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.inject.weld.internal.managed;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.annotation.Priority;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.NormalScope;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.Unmanaged;
import javax.enterprise.inject.spi.WithAnnotations;
import javax.inject.Scope;
import javax.inject.Singleton;

import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessInjectionTarget;

import javax.ws.rs.Path;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Application;
import javax.ws.rs.ext.Provider;

import org.glassfish.jersey.inject.weld.internal.data.BindingBeanPair;
import org.glassfish.jersey.inject.weld.internal.inject.InitializableSupplierInstanceBinding;
import org.glassfish.jersey.inject.weld.internal.scope.RequestScopeBean;
import org.glassfish.jersey.inject.weld.internal.inject.InitializableInstanceBinding;
import org.glassfish.jersey.inject.weld.internal.bean.BeanHelper;
import org.glassfish.jersey.inject.weld.internal.injector.ContextInjectionResolverImpl;
import org.glassfish.jersey.inject.weld.internal.injector.JerseyInjectionTarget;
import org.glassfish.jersey.inject.weld.internal.scope.CdiRequestScope;
import org.glassfish.jersey.inject.weld.spi.BootstrapPreinitialization;
import org.glassfish.jersey.internal.BootstrapBag;
import org.glassfish.jersey.internal.ServiceFinder;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.Binder;
import org.glassfish.jersey.internal.inject.Binding;
import org.glassfish.jersey.internal.inject.Bindings;
import org.glassfish.jersey.internal.inject.ClassBinding;
import org.glassfish.jersey.internal.inject.CustomAnnotationLiteral;
import org.glassfish.jersey.internal.inject.ForeignDescriptor;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InjectionResolver;
import org.glassfish.jersey.internal.inject.InjectionResolverBinding;
import org.glassfish.jersey.internal.inject.InstanceBinding;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.inject.ServiceHolder;
import org.glassfish.jersey.internal.inject.SupplierClassBinding;
import org.glassfish.jersey.internal.inject.SupplierInstanceBinding;

import org.glassfish.jersey.internal.util.collection.Ref;
import org.glassfish.jersey.internal.util.collection.Refs;
import org.glassfish.jersey.process.internal.RequestScope;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.ApplicationHandler;
import org.glassfish.jersey.server.ResourceConfig;
import org.jboss.weld.injection.producer.BasicInjectionTarget;
import org.jboss.weld.injection.producer.BeanInjectionTarget;

/**
 * CDI extension that handles CDI bootstrap events and registers Jersey's internally used components and components registered
 * using {@link Application}.
 */
class BinderRegisterExtension implements Extension {

    private AtomicBoolean registrationDone = new AtomicBoolean(false);
    private Supplier<BeanManager> beanManagerSupplier;
    private Ref<InjectionManager> serverInjectionManager = Refs.emptyRef();

    private BootstrapInjectionManager clientBootstrapInjectionManager = new BootstrapInjectionManager(RuntimeType.CLIENT);
    private WrappingInjectionManager serverBootstrapInjectionManager = new WrappingInjectionManager()
            .setInjectionManager(new BootstrapInjectionManager(RuntimeType.SERVER));
    private BootstrapBag bootstrapBag = new BootstrapBag();

    private final CachingBinder clientBindings = new CachingBinder(serverInjectionManager);
    private final CachingBinder serverBindings = new CachingBinder(serverInjectionManager) {
        @Override
        protected void configure() {
            install(new ContextInjectionResolverImpl.Binder(beanManagerSupplier));
            bind(InitializableInstanceBinding.from(Bindings.service(serverInjectionManager.get()).to(InjectionManager.class)));
        }
    };
    private final CachingBinder annotatedBeansBinder = new CachingBinder(serverInjectionManager);
    private final MergedBindings mergedBindings = new MergedBindings(serverBindings, clientBindings);

    private final List<InitializableInstanceBinding> initializableInstanceBindings = new LinkedList<>();
    private final List<InitializableSupplierInstanceBinding> initializableSupplierInstanceBindings = new LinkedList<>();
    private final MultivaluedMap<Type, BindingBeanPair> supplierClassBindings = new MultivaluedHashMap<>();
    private final MultivaluedMap<Type, BindingBeanPair> classBindings = new MultivaluedHashMap<>();
    private final List<JerseyInjectionTarget> jerseyInjectionTargets = new LinkedList<>();
    private final List<InjectionResolver> injectionResolvers = new LinkedList<>();
    private final Map<Class<?>, Class<? extends Annotation>> annotatedBeans = new HashMap<>();
    private final List<Class<Application>> applications = new LinkedList<>();
    final Set<Class<?>> managedBeans = new HashSet<>();

    /**
     * Ignores the classes which are manually added using bindings (through {@link Application} class) and scanned by CDI.
     * The manual adding is privileged and the beans scanned using CDI are ignored.
     * <p>
     * TODO: The method counts with the condition that the all bindings are known before the CDI scanning has been started,
     * can be changed during the migration from CDI SE to JAVA EE environment.
     *
     * @param pat processed type.
     * @param <T> type of the scanned bean.
     */
    <T> void ignoreManuallyRegisteredComponents(
            @Observes @WithAnnotations({ Path.class, Provider.class }) ProcessAnnotatedType<T> pat) {
        final AnnotatedType<T> annotatedType = pat.getAnnotatedType();
        for (Binding binding : mergedBindings.getBindings()) {
            if (ClassBinding.class.isAssignableFrom(binding.getClass())) {
                ClassBinding<?> classBinding = (ClassBinding<?>) binding;
                if (annotatedType.getJavaClass() == classBinding.getService()) {
                    pat.veto();
                    return;
                }
            } else if (InstanceBinding.class.isAssignableFrom(binding.getClass())) {
                InstanceBinding<?> instanceBinding = (InstanceBinding<?>) binding;
                if (annotatedType.getJavaClass() == instanceBinding.getService().getClass()) {
                    pat.veto();
                    return;
                }
            }
        }
        if (annotatedType.isAnnotationPresent(Path.class)) {
            boolean hasScope = false;
            for (Annotation annotation : annotatedType.getAnnotations()) {
                if (annotation.annotationType().isAnnotationPresent(Scope.class)
                        || annotation.annotationType().isAnnotationPresent(NormalScope.class)) {
                    hasScope = true;
                    break;
                }
            }
            if (!hasScope) {
                annotatedBeans.put(annotatedType.getJavaClass(), javax.enterprise.context.RequestScoped.class);
                pat.configureAnnotatedType().add(javax.enterprise.context.RequestScoped.Literal.INSTANCE);
            }
        }


    }

    <T> void registerJerseyRequestScopedResources(
            @Observes @WithAnnotations(RequestScoped.class) ProcessAnnotatedType<T> pat) {
        if (pat.getAnnotatedType().isAnnotationPresent(RequestScoped.class)
                && !pat.getAnnotatedType().isAnnotationPresent(javax.enterprise.context.RequestScoped.class)) {
            pat.configureAnnotatedType().remove(a -> RequestScoped.class.isInstance(a))
                    .add(javax.enterprise.context.RequestScoped.Literal.INSTANCE);
            annotatedBeans.put(pat.getAnnotatedType().getJavaClass(), javax.enterprise.context.RequestScoped.class);
        }
    }

    void processRegistrars(@Observes BeforeBeanDiscovery beforeBeanDiscovery, BeanManager beanManager) {
        CdiInjectionManagerFactoryBase.setBeanManager(beanManager);
        processRegistrars();
    }

    void handleRequestScoped(
            @Observes @WithAnnotations({javax.enterprise.context.RequestScoped.class}) ProcessAnnotatedType<?> pat) {
        final Class<?> javaClass = pat.getAnnotatedType().getJavaClass();
        if (isJaxrs(javaClass) && isNotJerseyInternal(javaClass)) {
            pat.configureAnnotatedType().add(CustomAnnotationLiteral.INSTANCE);
            annotatedBeans.put(javaClass, javax.enterprise.context.RequestScoped.class);
        }
    }

    <T> void handleApplicationScoped(
            @Observes @WithAnnotations({ApplicationScoped.class}) ProcessAnnotatedType<T> pat) {
        final Class<T> javaClass = pat.getAnnotatedType().getJavaClass();
        if (Application.class.isAssignableFrom(javaClass)) {
            pat.veto();
            applications.add((Class<Application>) javaClass);
        } else if (isJaxrs(javaClass) && isNotJerseyInternal(javaClass)) {
            pat.configureAnnotatedType().add(CustomAnnotationLiteral.INSTANCE);
            annotatedBeans.put(javaClass, ApplicationScoped.class);
        }
    }

    void handleDependent(@Observes @WithAnnotations({Dependent.class}) ProcessAnnotatedType<?> pat) {
        final Class<?> javaClass = pat.getAnnotatedType().getJavaClass();
        if (isJaxrs(javaClass) && isNotJerseyInternal(javaClass)) {
            pat.configureAnnotatedType().add(CustomAnnotationLiteral.INSTANCE);
            annotatedBeans.put(javaClass, Dependent.class);
        }
    }

    void handleSessionScoped(@Observes @WithAnnotations({SessionScoped.class}) ProcessAnnotatedType<?> pat) {
        final Class<?> javaClass = pat.getAnnotatedType().getJavaClass();
        if (isJaxrs(javaClass) && isNotJerseyInternal(javaClass)) {
            pat.configureAnnotatedType().add(CustomAnnotationLiteral.INSTANCE);
            annotatedBeans.put(javaClass, SessionScoped.class);
        }
    }

    void registerSingletonSubResources(@Observes @WithAnnotations({Singleton.class}) ProcessAnnotatedType<?> pat){
        final Class<?> resourceClass = pat.getAnnotatedType().getJavaClass();
        if (resourceClass.getAnnotation(Path.class) != null) {
            annotatedBeans.put(resourceClass, Singleton.class);
        } else if (BeanHelper.isResourceClass(resourceClass)) {
            annotatedBeans.put(resourceClass, Singleton.class);
        }
    }

    void registerJerseyProviders(@Observes @WithAnnotations({Priority.class}) ProcessAnnotatedType<?> pat) {
        final Class<?> javaClass = pat.getAnnotatedType().getJavaClass();
        if (!isNotJerseyInternal(javaClass)) {
            pat.veto(); //veto Jersey internal
        }
        annotatedBeans.put(javaClass, Priority.class);
    }

    /**
     * Wraps all JAX-RS components by Jersey-specific injection target.
     *
     * @param pit process injection target.
     * @param <T> type of the processed injection target.
     */
    public <T> void observeInjectionTarget(@Observes ProcessInjectionTarget<T> pit) {
        if (!BeanInjectionTarget.class.isInstance(pit.getInjectionTarget())) {
            return;
        }
        BasicInjectionTarget<T> it = (BasicInjectionTarget<T>) pit.getInjectionTarget();
        JerseyInjectionTarget<T> jerseyInjectionTarget =
                new JerseyInjectionTarget<>(it, pit.getAnnotatedType().getJavaClass());
        jerseyInjectionTargets.add(jerseyInjectionTarget);
        pit.setInjectionTarget(jerseyInjectionTarget);
    }

    /**
     * Takes all registered bindings and registers them in {@link BeanManager}.
     * <p>
     * Method should register only Jersey internal components and class/instances registered using {@link Application}. Registered
     * classes/instances have priority therefore CDI scanning should veto these classes/instances during {
     *
     * @param abd         {@code AfterBeanDiscovery} event.
     * @param beanManager current {@code BeanManager}.
     * @link ProcessAnnotatedType} bootstrap phase.
     */
    void registerBeans(@Observes AfterBeanDiscovery abd, BeanManager beanManager) {
        serverInjectionManager.set(new CdiInjectionManager(beanManager, mergedBindings));

        beanManagerSupplier = () -> beanManager; // set bean manager supplier to be called by bindings#configure
        CdiInjectionManagerFactoryBase.setBeanManager(beanManager);

        registerApplicationHandler(beanManager);

        registrationDone.set(true); //

        final List<InjectionResolver> contextInjectionResolvers = serverBindings.getBindings().stream()
                .filter(binding -> InjectionResolverBinding.class.isAssignableFrom(binding.getClass()))
                .map(InjectionResolverBinding.class::cast)
                .map(InjectionResolverBinding::getResolver)
                .collect(Collectors.toList());

        injectionResolvers.addAll(contextInjectionResolvers);

        /*
         * Provide registered InjectionResolvers to Jersey's components which has been discovered by CDI in
         * ProcessInjectionTarget bootstrap phase.
         */
        jerseyInjectionTargets.forEach(injectionTarget -> injectionTarget.setInjectionResolvers(injectionResolvers));

        registerBeans(RuntimeType.SERVER, this.serverBindings, abd, beanManager);
        registerBeans(RuntimeType.CLIENT, this.clientBindings, abd, beanManager);

        abd.addBean(new RequestScopeBean(beanManager));

        addAnnotatedBeans(abd, beanManager);

        serverBootstrapInjectionManager.setInjectionManager(serverInjectionManager.get());
        ((CdiInjectionManager) serverInjectionManager.get()).managedBeans = managedBeans;
    }

    private void registerBeans(RuntimeType runtimeType, CachingBinder binder, AfterBeanDiscovery abd,
                               BeanManager beanManager) {
        final Collection<Binding> bindings = binder.getBindings();
        binder.setReadOnly();

        allBindingsLabel:
        for (Binding binding : bindings) {
            if (ClassBinding.class.isAssignableFrom(binding.getClass())) {
                if (RuntimeType.CLIENT == runtimeType) {
                    for (Type contract : ((ClassBinding<?>) binding).getContracts()) {
                        final List<BindingBeanPair> preregistered = classBindings.get(contract);
                        if (preregistered != null && preregistered.size() == 1) {
                            BeanHelper.updateBean(
                                    (ClassBinding<?>) binding, preregistered.get(0), injectionResolvers, beanManager);
                            continue allBindingsLabel;
                        }
                    }
                }
                BindingBeanPair pair = BeanHelper.registerBean(
                        runtimeType, (ClassBinding<?>) binding, abd, injectionResolvers, beanManager);
                for (Type contract : ((ClassBinding<?>) binding).getContracts()) {
                    classBindings.add(contract, pair);
                }
            } else if (SupplierClassBinding.class.isAssignableFrom(binding.getClass())) {
                if (RuntimeType.CLIENT == runtimeType) {
                    for (Type contract : ((SupplierClassBinding<?>) binding).getContracts()) {
                        final List<BindingBeanPair> preregistered = supplierClassBindings.get(contract);
                        if (preregistered != null && preregistered.size() == 1) {
                            BeanHelper.updateSupplierBean(
                                    (SupplierClassBinding<?>) binding, preregistered.get(0), injectionResolvers, beanManager);
                            continue allBindingsLabel;
                        }
                    }
                }
                BindingBeanPair pair = BeanHelper.registerSupplier(
                        runtimeType, (SupplierClassBinding<?>) binding, abd, injectionResolvers, beanManager);
                if (pair != null) {
                    for (Type contract : ((SupplierClassBinding<?>) binding).getContracts()) {
                        supplierClassBindings.add(contract, pair);
                    }
                }
            } else if (InitializableInstanceBinding.class.isAssignableFrom(binding.getClass())) {
                if (RuntimeType.SERVER == runtimeType
                        || !matchInitializableInstanceBinding((InitializableInstanceBinding<?>) binding)) {
                    initializableInstanceBindings.add((InitializableInstanceBinding<?>) binding);
                    BeanHelper.registerBean(
                            runtimeType, (InitializableInstanceBinding<?>) binding, abd, injectionResolvers, beanManager);
                }
            } else if (InitializableSupplierInstanceBinding.class.isInstance(binding)) {
                if (RuntimeType.SERVER == runtimeType
                        || !matchInitializableSupplierInstanceBinding((InitializableSupplierInstanceBinding) binding)) {
                    initializableSupplierInstanceBindings.add((InitializableSupplierInstanceBinding) binding);
                    BeanHelper.registerSupplier(runtimeType, (InitializableSupplierInstanceBinding<?>) binding, abd, beanManager);
                }
            }
        }
    }

    private void addAnnotatedBeans(AfterBeanDiscovery abd, BeanManager beanManager) {
        for (Map.Entry<Class<?>, Class<? extends Annotation>> contract : annotatedBeans.entrySet()) {
            for (Binding binding : serverBindings.getBindings()) {
                if (ClassBinding.class.isInstance(binding)) {
                    if (((ClassBinding) binding).getService() == contract.getClass()) {
                        break;
                    }
                }
                if (InitializableInstanceBinding.class.isInstance(binding)) {
                    if (((InitializableInstanceBinding) binding).getImplementationType() == contract.getClass()) {
                        break;
                    }
                }
            }
            if (isNotJerseyInternal(contract.getKey())) {
                if (beanManager.getBeans(contract.getKey()).isEmpty()) {
                    final ClassBinding<?> binding = bind(contract.getKey(), annotatedBeansBinder);
                    if (Singleton.class.equals(contract.getValue())) {
                        binding.in(Singleton.class);
                    }
                }
                managedBeans.add(contract.getKey()); // add either way
            }
        }

        registerBeans(RuntimeType.SERVER, annotatedBeansBinder, abd, beanManager);
        serverBindings.getBindings().addAll(annotatedBeansBinder.getBindings());
    }

    private void registerApplicationHandler(BeanManager beanManager) {
        final ResourceConfig resourceConfig = new ResourceConfig();

        for (Class<Application> applicationClass : applications) {
            bindApplication(applicationClass, resourceConfig, beanManager);
        }

        new ApplicationHandler(resourceConfig);
    }

    private void bindApplication(Class<Application> applicationClass, ResourceConfig resourceConfig, BeanManager beanManager) {
        final Application application = new Unmanaged<>(applicationClass).newInstance().produce().get();

        for (Class<?> clazz : application.getClasses()) {
            if (beanManager.getBeans(clazz).isEmpty()) {
                // prevent double registration of a class
                // bind(clazz, binder);
                resourceConfig.register(clazz);
            } else if (!annotatedBeans.containsKey(clazz)) {
                annotatedBeans.put(clazz, Provider.class);
            }
        }
        for (Object singleton : application.getSingletons()) {
            final Class<?> clazz = singleton.getClass();
            if (beanManager.getBeans(clazz).isEmpty()) {
                // prevent double registration of a class
//                final InstanceBinding<?> binding = binder.bind(singleton);
//                toSuper(clazz, binding);
                resourceConfig.register(singleton);
            } else if (!annotatedBeans.containsKey(clazz)) {
                annotatedBeans.put(clazz, Provider.class);
            }
        }
    }

//    private void bindApplication(Class<Application> applicationClass, AbstractBinder binder, BeanManager beanManager) {
//        final Application application = new Unmanaged<>(applicationClass).newInstance().produce().get();
//        final CommonConfig commonConfig = new CommonConfig(RuntimeType.SERVER, ComponentBag.INCLUDE_ALL);
//
//        for (Class<?> clazz : application.getClasses()) {
//            if (beanManager.getBeans(clazz).isEmpty()) {
//                // prevent double registration of a class
//                // bind(clazz, binder);
//                commonConfig.register(clazz);
//            } else if (!annotatedBeans.containsKey(clazz)) {
//                annotatedBeans.put(clazz, Provider.class);
//            }
//        }
//        for (Object singleton : application.getSingletons()) {
//            final Class<?> clazz = singleton.getClass();
//            if (beanManager.getBeans(clazz).isEmpty()) {
//                // prevent double registration of a class
////                final InstanceBinding<?> binding = binder.bind(singleton);
////                toSuper(clazz, binding);
//                commonConfig.register(singleton);
//            } else if (!annotatedBeans.containsKey(clazz)) {
//                annotatedBeans.put(clazz, Provider.class);
//            }
//        }
//    }

    private static <T> ClassBinding<T> bind(Class<T> clazz, AbstractBinder binder) {
        final ClassBinding<T> binding = binder.bindAsContract(clazz);
        return toSuper(clazz, binding);
    }

    private static <T extends Binding> T toSuper(Class<?> clazz, T binding) {
        Class<?> superClass = clazz;
        while (superClass != null) {
            superClass = superClass.getSuperclass();
            if (superClass != null) {
                binding.to(superClass);
            }
        }
        for (Class<?> intf : clazz.getInterfaces()){
            binding.to(intf);
        }
        return binding;
    }

//    // Check first if a class is a JAX-RS resource, and only if so check with validation.
//    // This prevents unnecessary warnings being logged for pure CDI beans.
//    private final Cache<Class<?>, Boolean> jaxRsResourceCache = new Cache<>(
//            clazz -> Resource.from(clazz, true) != null && Resource.from(clazz) != null);
//
//    public boolean isJaxRsResource(Class<?> resource) {
//        return jaxRsResourceCache.apply(resource);
//    }

    private boolean isJaxrs(Class<?> clazz) {
        return Providers.isJaxRsProvider(clazz) || BeanHelper.isResourceClass(clazz) || isJerseyRegistrable(clazz);
    }

    private boolean isJerseyRegistrable(Class<?> clazz) {
        return Feature.class.isAssignableFrom(clazz) || DynamicFeature.class.isAssignableFrom(clazz);
    }

    private boolean isNotJerseyInternal(Class<?> clazz) {
        final Package pkg = clazz.getPackage();
        if (pkg == null) { // Class.getPackage() could return null
            return false;
        }

        final String pkgName = pkg.getName();
        return !pkgName.startsWith("org.glassfish.jersey")
                || pkgName.startsWith("org.glassfish.jersey.examples")
                || pkgName.startsWith("org.glassfish.jersey.tests");
    }

    private boolean matchInitializableInstanceBinding(InitializableInstanceBinding candidate) {
        for (InitializableInstanceBinding iib : initializableInstanceBindings) {
            if (iib.matches(candidate).matches()) {
                return true;
            }
        }
        return false;
    }

    private boolean matchInitializableSupplierInstanceBinding(InitializableSupplierInstanceBinding candidate) {
        for (InitializableSupplierInstanceBinding isib : initializableSupplierInstanceBindings) {
            if (isib.matches(candidate).matches()) {
                return true;
            }
        }
        return false;
    }


    /** To be used by the tests only */
    public void register(BeforeBeanDiscovery beforeBeanDiscovery, Binding binding) {
        register(RuntimeType.SERVER, binding);
    }

    /** To be used by the tests only */
    public void register(BeforeBeanDiscovery beforeBeanDiscovery, Iterable<Binding> bindings) {
        register(RuntimeType.SERVER, bindings);
    }

    private void register(RuntimeType runtimeType, Binding binding) {
        final AbstractBinder bindings = runtimeType == RuntimeType.CLIENT ? clientBindings : serverBindings;
        if (InstanceBinding.class.isInstance(binding)) {
            bindings.bind(InitializableInstanceBinding.from((InstanceBinding) binding));
        } else if (SupplierInstanceBinding.class.isInstance(binding)) {
            bindings.bind(InitializableSupplierInstanceBinding.from((SupplierInstanceBinding) binding));
        } else {
            bindings.bind(binding);
        }
    }

    private void register(RuntimeType runtimeType, Iterable<Binding> bindings) {
        for (Binding binding : bindings) {
            register(runtimeType, binding);
        }
    }

    private void processRegistrars() {
        final List<BootstrapPreinitialization> registrars = new LinkedList<>();
        for (BootstrapPreinitialization registrar : ServiceFinder.find(BootstrapPreinitialization.class)) {
            registrars.add(registrar);
        }
        for (BootstrapPreinitialization registrar : registrars) {
            registrar.register(RuntimeType.SERVER, serverBindings);
        }

        for (BootstrapPreinitialization registrar : registrars) {
            registrar.register(RuntimeType.CLIENT, clientBindings);
        }
    }

    InjectionManager getInjectionManager(RuntimeType runtimeType) {
        if (RuntimeType.CLIENT == runtimeType) {
            return registrationDone.get()
                    ? new CdiClientInjectionManager(beanManagerSupplier.get(), mergedBindings)
                    : clientBootstrapInjectionManager;
        } else {
            return registrationDone.get() ? serverInjectionManager.get() : serverBootstrapInjectionManager;
        }
    }

    /**
     * Injection manager used during the bootstrap. It is used to create the actual beans in the beans manager.
     * Other InjectionManagers sets the beans (beans binding) by a value provided in runtime.
     */
    private class BootstrapInjectionManager implements InjectionManager {

        private final RuntimeType runtimeType;

        private BootstrapInjectionManager(RuntimeType runtimeType) {
            this.runtimeType = runtimeType;
        }

        @Override
        public void completeRegistration() {
            //noop
        }

        @Override
        public void shutdown() {
            //noop
        }

        @Override
        public boolean isShutdown() {
            return false;
        }

        @Override
        public void register(Binding binding) {
            BinderRegisterExtension.this.register(runtimeType, binding);
        }

        @Override
        public void register(Iterable<Binding> descriptors) {
           for (Binding binding : descriptors) {
               register(binding);
           }
        }

        @Override
        public void register(Binder binder) {
            register(binder.getBindings());
        }

        @Override
        public void register(Object provider) throws IllegalArgumentException {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean isRegistrable(Class<?> clazz) {
            return false;
        }

        @Override
        public <T> T createAndInitialize(Class<T> createMe) {
            if (RequestScope.class == createMe) {
                return (T) new CdiRequestScope();
            }
            if (isNotJerseyInternal(createMe)) {
                return null;
            }
            try {
                Constructor<T> constructor = createMe.getConstructor();
                return constructor.newInstance();
            } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
                return null;
            }
        }

        @Override
        public <T> T create(Class<T> createMe) {
            return createAndInitialize(createMe);
        }

        @Override
        public <T> List<ServiceHolder<T>> getAllServiceHolders(Class<T> contractOrImpl, Annotation... qualifiers) {
            return Collections.EMPTY_LIST;
        }

        @Override
        public <T> T getInstance(Class<T> contractOrImpl, Annotation... qualifiers) {
            return getInstance(contractOrImpl);
        }

        @Override
        public <T> T getInstance(Class<T> contractOrImpl, String classAnalyzer) {
            return getInstance(contractOrImpl);
        }

        @Override
        public <T> T getInstance(Class<T> contractOrImpl) {
            return createAndInitialize(contractOrImpl);
        }

        @Override
        public <T> T getInstance(Type contractOrImpl) {
            return (T) createAndInitialize((Class) contractOrImpl);
        }

        @Override
        public Object getInstance(ForeignDescriptor foreignDescriptor) {
            throw new UnsupportedOperationException();
        }

        @Override
        public ForeignDescriptor createForeignDescriptor(Binding binding) {
            throw new UnsupportedOperationException();
        }

        @Override
        public <T> List<T> getAllInstances(Type contractOrImpl) {
            final T t = getInstance(contractOrImpl);
            return t != null ? Collections.singletonList(t) : Collections.EMPTY_LIST;
        }

        @Override
        public void inject(Object injectMe) {
           // noop;
        }

        @Override
        public void inject(Object injectMe, String classAnalyzer) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void preDestroy(Object preDestroyMe) {
            //noop
        }
    }

    /**
     * AbstractBinder that supports calling {@link #getBindings()} multiple times by caching the result.
     * Each additional binding is added to the cache by the next call of {@link #getBindings()}.
     * When {@link #setReadOnly()} is called, no additional binding is added to the cache.
     */
    private class CachingBinder extends AbstractBinder {
        private final Ref<InjectionManager> injectionManager;
        private AbstractBinder temporaryBinder = new TemporaryBinder();
        private final Collection<Binding> bindings = new LinkedList<>();

        private CachingBinder(Ref<InjectionManager> injectionManager) {
            this.injectionManager = injectionManager;
        }

        @Override
        protected void configure() {
            // noop
        }

        @Override
        public <T> ClassBinding<T> bind(Class<T> serviceType) {
            return temporaryBinder.bind(serviceType);
        }

        @Override
        public Binding bind(Binding binding) {
            return temporaryBinder.bind(binding);
        }

        @Override
        public <T> ClassBinding<T> bindAsContract(GenericType<T> serviceType) {
            return temporaryBinder.bindAsContract(serviceType);
        }

        @Override
        public <T> ClassBinding<T> bindAsContract(Class<T> serviceType) {
            return temporaryBinder.bindAsContract(serviceType);
        }

        @Override
        public ClassBinding<Object> bindAsContract(Type serviceType) {
            return temporaryBinder.bindAsContract(serviceType);
        }

        @Override
        public <T> InstanceBinding<T> bind(T service) {
            return temporaryBinder.bind(service);
        }

        @Override
        public <T> SupplierClassBinding<T> bindFactory(
                Class<? extends Supplier<T>> supplierType, Class<? extends Annotation> supplierScope) {
            return temporaryBinder.bindFactory(supplierType, supplierScope);
        }

        @Override
        public <T> SupplierClassBinding<T> bindFactory(Class<? extends Supplier<T>> supplierType) {
            return temporaryBinder.bindFactory(supplierType);
        }

        @Override
        public <T> SupplierInstanceBinding<T> bindFactory(Supplier<T> factory) {
            return temporaryBinder.bindFactory(factory);
        }

        @Override
        public <T extends InjectionResolver> InjectionResolverBinding<T> bind(T resolver) {
            return temporaryBinder.bind(resolver);
        }

        @Override
        public Collection<Binding> getBindings() {
            if (!readOnly) {
                if (registrationDone.get()) {
                    bindings.addAll(super.getBindings());
                }
                final Collection<Binding> newBindings = temporaryBinder.getBindings();
                for (Binding binding : newBindings) {
                    if (InstanceBinding.class.isAssignableFrom(binding.getClass())) {
                        binding = InitializableInstanceBinding.from((InstanceBinding) binding);
                    } else if (SupplierInstanceBinding.class.isAssignableFrom(binding.getClass())) {
                        binding = InitializableSupplierInstanceBinding.from((SupplierInstanceBinding) binding);
                    }
                    bindings.add(binding);
                }
                temporaryBinder = new TemporaryBinder();
            }
            return bindings;
        }

        private boolean readOnly = false;

        void setReadOnly() {
            readOnly = true;
        }

        private class TemporaryBinder extends AbstractBinder {

            @Override
            protected void configure() {
                // do nothing
            }
        }
    }

    private static class MergedBindings implements Binder {
        private final AbstractBinder first;
        private final AbstractBinder second;


        private MergedBindings(AbstractBinder first, AbstractBinder second) {
            this.first = first;
            this.second = second;
        }

        public Collection<Binding> getBindings() {
            final Collection<Binding> firstBindings = first.getBindings();
            final Collection<Binding> secondBindings = second.getBindings();

            Collection<Binding> merged = new Collection<Binding>() {
                @Override
                public int size() {
                    return firstBindings.size() + secondBindings.size();
                }

                @Override
                public boolean isEmpty() {
                    return firstBindings.isEmpty() && secondBindings.isEmpty();
                }

                @Override
                public boolean contains(Object o) {
                    return firstBindings.contains(o) || secondBindings.contains(o);
                }

                @Override
                public Iterator<Binding> iterator() {
                    final Iterator<Binding> firstIterator = firstBindings.iterator();
                    final Iterator<Binding> secondIterator = secondBindings.iterator();
                    return new Iterator<Binding>() {
                        @Override
                        public boolean hasNext() {
                            return firstIterator.hasNext() || secondIterator.hasNext();
                        }

                        @Override
                        public Binding next() {
                            return firstIterator.hasNext() ? firstIterator.next() : secondIterator.next();
                        }
                    };
                }

                // Used by IDE while debugging
                @Override
                public Object[] toArray() {
                    Object[] array = new Object[size()];
                    final Iterator<Binding> bindingIterator = iterator();
                    int i = 0;
                    while (bindingIterator.hasNext()) {
                        array[i++] = bindingIterator.next();
                    }
                    return array;
                }

                @Override
                public <T> T[] toArray(T[] a) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public boolean add(Binding binding) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public boolean remove(Object o) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public boolean containsAll(Collection<?> c) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public boolean addAll(Collection<? extends Binding> c) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public boolean removeAll(Collection<?> c) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public boolean retainAll(Collection<?> c) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public void clear() {
                    throw new UnsupportedOperationException();
                }
            };
            return merged;
        }
    }
}