CommonConfigTest.java

/*
 * Copyright (c) 2012, 2022 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.tests.e2e.common.model.internal;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.ws.rs.Priorities;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;

import javax.annotation.Priority;
import javax.inject.Inject;

import org.glassfish.jersey.inject.hk2.Hk2InjectionManagerFactory;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.Injections;
import org.glassfish.jersey.internal.inject.ProviderBinder;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.model.ContractProvider;
import org.glassfish.jersey.model.internal.CommonConfig;
import org.glassfish.jersey.model.internal.ComponentBag;
import org.glassfish.jersey.model.internal.ManagedObjectsFinalizer;
import org.glassfish.jersey.model.internal.RankedComparator;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * Test cases for {@link javax.ws.rs.core.Configuration}.
 *
 * @author Michal Gajdos
 */
public class CommonConfigTest {

    private CommonConfig config;

    @BeforeEach
    public void setUp() throws Exception {
        config = new CommonConfig(null, ComponentBag.INCLUDE_ALL);
    }

    @Test
    public void testGetProperties() throws Exception {
        try {
            config.getConfiguration().getProperties().put("foo", "bar");
            fail("Returned properties collection should be immutable.");
        } catch (final Exception e) {
            // OK.
        }
    }

    @Test
    public void testSetProperties() throws Exception {
        config = config.property("foo", "bar");
        assertEquals("bar", config.getConfiguration().getProperty("foo"));

        final Map<String, String> properties = new HashMap<>();
        properties.put("hello", "world");
        config = config.setProperties(properties);

        assertEquals(1, config.getConfiguration().getProperties().size());
        assertEquals("world", config.getConfiguration().getProperty("hello"));

        properties.put("one", "two");
        assertEquals(1, config.getConfiguration().getProperties().size());
        assertNull(config.getConfiguration().getProperty("one"));

        config = config.setProperties(new HashMap<>());
        assertTrue(config.getConfiguration().getProperties().isEmpty());
    }

    @Test
    public void testSetGetProperty() throws Exception {
        config = config.property("foo", "bar");
        assertEquals("bar", config.getConfiguration().getProperty("foo"));

        config.property("hello", "world");
        config.property("foo", null);

        assertEquals(null, config.getConfiguration().getProperty("foo"));
        assertEquals(1, config.getConfiguration().getProperties().size());
    }

    public static class EmptyFeature implements Feature {

        @Override
        public boolean configure(final FeatureContext configuration) {
            return true;
        }
    }

    public static class UnconfigurableFeature implements Feature {

        @Override
        public boolean configure(final FeatureContext configuration) {
            return false;
        }
    }

    public static class ComplexEmptyProvider implements ReaderInterceptor, ContainerRequestFilter, ExceptionMapper {

        @Override
        public void filter(final ContainerRequestContext requestContext) throws IOException {
            // Do nothing.
        }

        @Override
        public Object aroundReadFrom(final ReaderInterceptorContext context) throws IOException, WebApplicationException {
            return context.proceed();
        }

        @Override
        public Response toResponse(final Throwable exception) {
            throw new UnsupportedOperationException();
        }
    }

    public static class ComplexEmptyProviderFeature extends ComplexEmptyProvider implements Feature {

        @Override
        public boolean configure(final FeatureContext configuration) {
            throw new UnsupportedOperationException();
        }
    }

    @Test
    public void testReplaceWith() throws Exception {
        config.property("foo", "bar");
        final EmptyFeature emptyFeature = new EmptyFeature();
        config.register(emptyFeature);
        config.register(ComplexEmptyProvider.class, ExceptionMapper.class);

        final CommonConfig other = new CommonConfig(null, ComponentBag.INCLUDE_ALL);
        other.property("foo", "baz");
        other.register(UnconfigurableFeature.class);
        other.register(ComplexEmptyProvider.class, ReaderInterceptor.class, ContainerRequestFilter.class);

        assertEquals("baz", other.getProperty("foo"));
        assertEquals(1, other.getProperties().size());
        assertEquals(2, other.getClasses().size());
        assertEquals(0, other.getInstances().size());
        assertEquals(2, other.getContracts(ComplexEmptyProvider.class).size());
        assertTrue(other.getContracts(ComplexEmptyProvider.class).containsKey(ReaderInterceptor.class));
        assertTrue(other.getContracts(ComplexEmptyProvider.class).containsKey(ContainerRequestFilter.class));

        other.loadFrom(config);

        assertEquals("bar", other.getProperty("foo"));
        assertEquals(1, other.getProperties().size());
        assertEquals(1, other.getClasses().size());
        assertEquals(1, other.getInstances().size());
        assertEquals(1, other.getContracts(ComplexEmptyProvider.class).size());
        assertSame(ExceptionMapper.class, other.getContracts(ComplexEmptyProvider.class).keySet().iterator().next());
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testGetFeatures() throws Exception {
        final EmptyFeature emptyFeature = new EmptyFeature();
        final UnconfigurableFeature unconfigurableFeature = new UnconfigurableFeature();
        final ComplexEmptyProviderFeature providerFeature = new ComplexEmptyProviderFeature();

        config.register(emptyFeature);
        config.register(unconfigurableFeature);
        config.register(providerFeature, ReaderInterceptor.class);

        assertFalse(config.getConfiguration().isEnabled(emptyFeature));
        assertFalse(config.getConfiguration().isEnabled(unconfigurableFeature));
        assertFalse(config.getConfiguration().isEnabled(providerFeature));

        assertTrue(config.getConfiguration().isRegistered(emptyFeature));
        assertTrue(config.getConfiguration().isRegistered(unconfigurableFeature));
        assertTrue(config.getConfiguration().isRegistered(providerFeature));
    }


    @Test
    // Regression test for JERSEY-1638.
    public void testGetNonExistentProviderContractsASEmptyMap() throws Exception {
        assertTrue(config.getConfiguration().getContracts(CommonConfigTest.class).isEmpty());
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testGetClasses() throws Exception {
        _testCollectionsCommon("GetProviderClasses", config.getClasses(), EmptyFeature.class);

        config.register(ComplexEmptyProviderFeature.class,
                WriterInterceptor.class, ReaderInterceptor.class, ContainerRequestFilter.class);
        assertEquals(1, config.getClasses().size());

        config.register(EmptyFeature.class);

        final Set<Class<?>> providerClasses = config.getClasses();
        assertEquals(2, providerClasses.size());
        assertTrue(providerClasses.contains(ComplexEmptyProviderFeature.class));
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testGetInstances() throws Exception {
        _testCollectionsCommon("GetProviderInstances", config.getInstances(), new EmptyFeature());

        final ComplexEmptyProviderFeature providerFeature1 = new ComplexEmptyProviderFeature();
        config.register(providerFeature1, WriterInterceptor.class);
        assertEquals(1, config.getInstances().size());
        assertTrue(config.getInstances().contains(providerFeature1));

        final EmptyFeature emptyFeature = new EmptyFeature();
        config.register(emptyFeature);
        assertEquals(2, config.getInstances().size());
        assertTrue(config.getInstances().contains(emptyFeature));

        final ComplexEmptyProviderFeature providerFeature2 = new ComplexEmptyProviderFeature();
        config.register(providerFeature2, ReaderInterceptor.class, ContainerRequestFilter.class);
        assertEquals(2, config.getInstances().size());
        assertFalse(config.getInstances().contains(providerFeature2));
    }

    @Test
    public void testRegisterClass() throws Exception {
        try {
            final Class clazz = null;
            //noinspection ConstantConditions
            config.register(clazz);
            fail("Cannot register null.");
        } catch (final IllegalArgumentException e) {
            // OK.
        }

        for (int i = 0; i < 2; i++) {
            config.register(ComplexEmptyProvider.class);
        }

        final ContractProvider contractProvider =
                config.getComponentBag().getModel(ComplexEmptyProvider.class);
        final Set<Class<?>> contracts = contractProvider.getContracts();

        assertEquals(3, contracts.size());
        assertTrue(contracts.contains(ReaderInterceptor.class));
        assertTrue(contracts.contains(ContainerRequestFilter.class));
        assertTrue(contracts.contains(ExceptionMapper.class));

        assertTrue(config.isRegistered(ComplexEmptyProvider.class));
    }

    @Test
    public void testRegisterInstance() throws Exception {
        try {
            config.register(null);
            fail("Cannot register null.");
        } catch (final IllegalArgumentException e) {
            // OK.
        }

        final ComplexEmptyProvider[] ceps = new ComplexEmptyProvider[2];
        for (int i = 0; i < 2; i++) {
            ceps[i] = new ComplexEmptyProvider();
            config.register(ceps[i]);
        }

        final ContractProvider contractProvider =
                config.getComponentBag().getModel(ComplexEmptyProvider.class);
        final Set<Class<?>> contracts = contractProvider.getContracts();

        assertEquals(3, contracts.size());
        assertTrue(contracts.contains(ReaderInterceptor.class));
        assertTrue(contracts.contains(ContainerRequestFilter.class));
        assertTrue(contracts.contains(ExceptionMapper.class));

        assertTrue(config.isRegistered(ComplexEmptyProvider.class));
        assertTrue(config.isRegistered(ceps[0]));
        assertFalse(config.isRegistered(ceps[1]));
    }

    @Test
    public void testRegisterClassInstanceClash() throws Exception {
        final ComplexEmptyProvider complexEmptyProvider = new ComplexEmptyProvider();

        config.register(ComplexEmptyProvider.class);
        config.register(complexEmptyProvider);
        config.register(ComplexEmptyProvider.class);

        assertTrue(config.getClasses().contains(ComplexEmptyProvider.class));
        assertFalse(config.getInstances().contains(complexEmptyProvider));

        final ContractProvider contractProvider =
                config.getComponentBag().getModel(ComplexEmptyProvider.class);
        final Set<Class<?>> contracts = contractProvider.getContracts();

        assertEquals(3, contracts.size());
        assertTrue(contracts.contains(ReaderInterceptor.class));
        assertTrue(contracts.contains(ContainerRequestFilter.class));
        assertTrue(contracts.contains(ExceptionMapper.class));
    }

    @Test
    public void testRegisterClassBingingPriority() throws Exception {
        try {
            final Class clazz = null;
            //noinspection ConstantConditions
            config.register(clazz, Priorities.USER);
            fail("Cannot register null.");
        } catch (final IllegalArgumentException e) {
            // OK.
        }

        for (final int priority : new int[]{Priorities.USER, Priorities.AUTHENTICATION}) {
            config.register(ComplexEmptyProvider.class, priority);

            final ContractProvider contractProvider =
                    config.getComponentBag().getModel(ComplexEmptyProvider.class);
            final Set<Class<?>> contracts = contractProvider.getContracts();

            assertEquals(3, contracts.size());
            assertTrue(contracts.contains(ReaderInterceptor.class));
            assertTrue(contracts.contains(ContainerRequestFilter.class));
            assertTrue(contracts.contains(ExceptionMapper.class));

            // All priorities are the same.
            assertEquals(Priorities.USER, contractProvider.getPriority(ReaderInterceptor.class));
            assertEquals(Priorities.USER, contractProvider.getPriority(ContainerRequestFilter.class));
            assertEquals(Priorities.USER, contractProvider.getPriority(ExceptionMapper.class));
        }
    }

    @Test
    public void testRegisterInstanceBingingPriority() throws Exception {
        try {
            config.register(null, Priorities.USER);
            fail("Cannot register null.");
        } catch (final IllegalArgumentException e) {
            // OK.
        }

        final Class<ComplexEmptyProvider> providerClass = ComplexEmptyProvider.class;

        for (final int priority : new int[]{Priorities.USER, Priorities.AUTHENTICATION}) {
            config.register(providerClass, priority);

            final CommonConfig commonConfig = config;
            final ContractProvider contractProvider =
                    commonConfig.getComponentBag().getModel(providerClass);
            final Set<Class<?>> contracts = contractProvider.getContracts();

            assertEquals(3, contracts.size()); // Feature is not there.
            assertTrue(contracts.contains(ReaderInterceptor.class));
            assertTrue(contracts.contains(ContainerRequestFilter.class));
            assertTrue(contracts.contains(ExceptionMapper.class));

            // All priorities are the same.
            assertEquals(Priorities.USER, contractProvider.getPriority(ReaderInterceptor.class));
            assertEquals(Priorities.USER, contractProvider.getPriority(ContainerRequestFilter.class));
            assertEquals(Priorities.USER, contractProvider.getPriority(ExceptionMapper.class));
        }
    }

    @Test
    public void testRegisterClassInstanceBindingPriorityClash() throws Exception {
        final ComplexEmptyProvider complexEmptyProvider = new ComplexEmptyProvider();

        config.register(ComplexEmptyProvider.class, Priorities.AUTHENTICATION);
        config.register(complexEmptyProvider, Priorities.USER);


        assertTrue(config.getClasses().contains(ComplexEmptyProvider.class));
        assertFalse(config.getInstances().contains(complexEmptyProvider));

        final ComponentBag componentBag = config.getComponentBag();
        final ContractProvider contractProvider =
                componentBag.getModel(ComplexEmptyProvider.class);
        final Set<Class<?>> contracts = contractProvider.getContracts();

        assertEquals(3, contracts.size()); // Feature is not there.
        assertTrue(contracts.contains(ReaderInterceptor.class));
        assertTrue(contracts.contains(ContainerRequestFilter.class));
        assertTrue(contracts.contains(ExceptionMapper.class));

        // All priorities are the same.
        assertEquals(Priorities.AUTHENTICATION, contractProvider.getPriority(ReaderInterceptor.class));
        assertEquals(Priorities.AUTHENTICATION, contractProvider.getPriority(ContainerRequestFilter.class));
        assertEquals(Priorities.AUTHENTICATION, contractProvider.getPriority(ExceptionMapper.class));
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testRegisterClassContracts() throws Exception {
        try {
            final Class clazz = null;
            //noinspection ConstantConditions
            config.register(clazz, ReaderInterceptor.class);
            fail("Cannot register null.");
        } catch (final IllegalArgumentException e) {
            // OK.
        }

        config.register(ComplexEmptyProvider.class,
                ReaderInterceptor.class, ContainerRequestFilter.class, WriterInterceptor.class);
        final ContractProvider contractProvider = config.getComponentBag().getModel(ComplexEmptyProvider.class);
        final Set<Class<?>> contracts = contractProvider.getContracts();
        assertEquals(2, contracts.size());
        assertTrue(contracts.contains(ReaderInterceptor.class), ReaderInterceptor.class + " is not registered.");
        assertTrue(contracts.contains(ContainerRequestFilter.class), ContainerRequestFilter.class + " is not registered.");
        assertFalse(contracts.contains(WriterInterceptor.class), WriterInterceptor.class + " should not be registered.");

        assertTrue(config.getInstances().isEmpty());
        assertTrue(config.getClasses().contains(ComplexEmptyProvider.class));
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testRegisterInstancesContracts() throws Exception {
        try {
            config.register(null, ReaderInterceptor.class);
            fail("Cannot register null.");
        } catch (final IllegalArgumentException e) {
            // OK.
        }

        final ComplexEmptyProvider complexEmptyProvider = new ComplexEmptyProvider();
        config.register(complexEmptyProvider,
                ReaderInterceptor.class, ContainerRequestFilter.class, WriterInterceptor.class);
        final ContractProvider contractProvider = config.getComponentBag().getModel(ComplexEmptyProvider.class);
        final Set<Class<?>> contracts = contractProvider.getContracts();
        assertEquals(2, contracts.size());
        assertTrue(contracts.contains(ReaderInterceptor.class), ReaderInterceptor.class + " is not registered.");
        assertTrue(contracts.contains(ContainerRequestFilter.class), ContainerRequestFilter.class + " is not registered.");
        assertFalse(contracts.contains(WriterInterceptor.class), WriterInterceptor.class + " should not be registered.");

        assertTrue(config.getInstances().contains(complexEmptyProvider));
        assertTrue(config.getClasses().isEmpty());
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testRegisterClassContractsFeatureNotInvoked() throws Exception {
        config.register(ComplexEmptyProviderFeature.class, ReaderInterceptor.class);
        assertFalse(config.getConfiguration().isEnabled(ComplexEmptyProviderFeature.class));
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testRegisterInstancesContractsFeatureNotInvoked() throws Exception {
        final ComplexEmptyProviderFeature feature = new ComplexEmptyProviderFeature();
        config.register(feature, ReaderInterceptor.class);
        assertFalse(config.getConfiguration().isEnabled(ComplexEmptyProviderFeature.class));
        assertFalse(config.getConfiguration().isEnabled(feature));
    }

    @Test
    public void testRegisterClassNullContracts() throws Exception {
        config.register(ComplexEmptyProvider.class, (Class) null);

        final ContractProvider contractProvider =
                config.getComponentBag().getModel(ComplexEmptyProvider.class);
        final Set<Class<?>> contracts = contractProvider.getContracts();

        assertEquals(0, contracts.size());
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testRegisterInstanceNullContracts() throws Exception {
        config.register(new ComplexEmptyProvider(), (Class) null);

        final ContractProvider contractProvider =
                config.getComponentBag().getModel(ComplexEmptyProvider.class);
        final Set<Class<?>> contracts = contractProvider.getContracts();

        assertEquals(0, contracts.size());
    }

    // Reproducer JERSEY-1637
    @Test
    public void testRegisterNullOrEmptyContracts() {
        final ComplexEmptyProvider provider = new ComplexEmptyProvider();

        config.register(ComplexEmptyProvider.class,  (Class<?>[]) null);
        assertFalse(config.getConfiguration().isRegistered(ComplexEmptyProvider.class));

        config.register(provider,  (Class<?>[]) null);
        assertFalse(config.getConfiguration().isRegistered(ComplexEmptyProvider.class));
        assertFalse(config.getConfiguration().isRegistered(provider));

        config.register(ComplexEmptyProvider.class,  new Class[0]);
        assertFalse(config.getConfiguration().isRegistered(ComplexEmptyProvider.class));

        config.register(provider,  new Class[0]);
        assertFalse(config.getConfiguration().isRegistered(ComplexEmptyProvider.class));
        assertFalse(config.getConfiguration().isRegistered(provider));
    }

    @Priority(300)
    public static class LowPriorityProvider implements WriterInterceptor, ReaderInterceptor {

        @Override
        public void aroundWriteTo(final WriterInterceptorContext context) throws IOException, WebApplicationException {
            // Do nothing.
        }

        @Override
        public Object aroundReadFrom(final ReaderInterceptorContext context) throws IOException, WebApplicationException {
            return context.proceed();
        }
    }

    @Priority(200)
    public static class MidPriorityProvider implements WriterInterceptor, ReaderInterceptor {

        @Override
        public void aroundWriteTo(final WriterInterceptorContext context) throws IOException, WebApplicationException {
            // Do nothing.
        }

        @Override
        public Object aroundReadFrom(final ReaderInterceptorContext context) throws IOException, WebApplicationException {
            return context.proceed();
        }
    }

    @Priority(100)
    public static class HighPriorityProvider implements WriterInterceptor, ReaderInterceptor {

        @Override
        public void aroundWriteTo(final WriterInterceptorContext context) throws IOException, WebApplicationException {
            // Do nothing.
        }

        @Override
        public Object aroundReadFrom(final ReaderInterceptorContext context) throws IOException, WebApplicationException {
            return context.proceed();
        }
    }

    @Test
    public void testProviderOrderManual() throws Exception {
        InjectionManager injectionManager = Injections.createInjectionManager();

        config.register(MidPriorityProvider.class, 500);
        config.register(LowPriorityProvider.class, 20);
        config.register(HighPriorityProvider.class, 150);

        ProviderBinder.bindProviders(config.getComponentBag(), injectionManager);

        injectionManager.completeRegistration();
        final Iterable<WriterInterceptor> allProviders =
                Providers.getAllProviders(injectionManager, WriterInterceptor.class, new RankedComparator<>());

        final Iterator<WriterInterceptor> iterator = allProviders.iterator();

        assertEquals(LowPriorityProvider.class, iterator.next().getClass());
        assertEquals(HighPriorityProvider.class, iterator.next().getClass());
        assertEquals(MidPriorityProvider.class, iterator.next().getClass());
        assertFalse(iterator.hasNext());
    }

    @Test
    public void testProviderOrderSemiAutomatic() throws Exception {
        InjectionManager injectionManager = Injections.createInjectionManager();

        config.register(MidPriorityProvider.class, 50);
        config.register(LowPriorityProvider.class, 2000);
        config.register(HighPriorityProvider.class);

        ProviderBinder.bindProviders(config.getComponentBag(), injectionManager);
        injectionManager.completeRegistration();
        final Iterable<WriterInterceptor> allProviders =
                Providers.getAllProviders(injectionManager, WriterInterceptor.class, new RankedComparator<>());

        final Iterator<WriterInterceptor> iterator = allProviders.iterator();

        assertEquals(MidPriorityProvider.class, iterator.next().getClass());
        assertEquals(HighPriorityProvider.class, iterator.next().getClass());
        assertEquals(LowPriorityProvider.class, iterator.next().getClass());
        assertFalse(iterator.hasNext());
    }

    @Test
    public void testProviderOrderAutomatic() throws Exception {
        InjectionManager injectionManager = Injections.createInjectionManager();
        config.register(MidPriorityProvider.class);
        config.register(LowPriorityProvider.class);
        config.register(HighPriorityProvider.class);

        ProviderBinder.bindProviders(config.getComponentBag(), injectionManager);
        injectionManager.completeRegistration();

        final Iterable<WriterInterceptor> allProviders =
                Providers.getAllProviders(injectionManager, WriterInterceptor.class, new RankedComparator<>());

        final Iterator<WriterInterceptor> iterator = allProviders.iterator();

        assertEquals(HighPriorityProvider.class, iterator.next().getClass());
        assertEquals(MidPriorityProvider.class, iterator.next().getClass());
        assertEquals(LowPriorityProvider.class, iterator.next().getClass());
        assertFalse(iterator.hasNext());
    }

    @Test
    @SuppressWarnings("unchecked")
    public void testProviderOrderDifForContracts() throws Exception {
        final Map<Class<?>, Integer> contracts = new IdentityHashMap<>();

        contracts.put(WriterInterceptor.class, ContractProvider.NO_PRIORITY);
        contracts.put(ReaderInterceptor.class, 2000);
        config.register(MidPriorityProvider.class, contracts);
        contracts.clear();

        contracts.put(WriterInterceptor.class, ContractProvider.NO_PRIORITY);
        contracts.put(ReaderInterceptor.class, 1000);
        config.register(LowPriorityProvider.class, contracts);
        contracts.clear();

        contracts.put(WriterInterceptor.class, ContractProvider.NO_PRIORITY);
        contracts.put(ReaderInterceptor.class, 3000);
        config.register(HighPriorityProvider.class, contracts);
        contracts.clear();

        InjectionManager injectionManager = Injections.createInjectionManager();
        ProviderBinder.bindProviders(config.getComponentBag(), injectionManager);

        injectionManager.completeRegistration();
        final Iterable<WriterInterceptor> writerInterceptors =
                Providers.getAllProviders(injectionManager, WriterInterceptor.class, new RankedComparator<>());

        final Iterator<WriterInterceptor> writerIterator = writerInterceptors.iterator();

        assertEquals(HighPriorityProvider.class, writerIterator.next().getClass());
        assertEquals(MidPriorityProvider.class, writerIterator.next().getClass());
        assertEquals(LowPriorityProvider.class, writerIterator.next().getClass());
        assertFalse(writerIterator.hasNext());

        final Iterable<ReaderInterceptor> readerInterceptors =
                Providers.getAllProviders(injectionManager, ReaderInterceptor.class, new RankedComparator<>());

        final Iterator<ReaderInterceptor> readerIterator = readerInterceptors.iterator();

        assertEquals(LowPriorityProvider.class, readerIterator.next().getClass());
        assertEquals(MidPriorityProvider.class, readerIterator.next().getClass());
        assertEquals(HighPriorityProvider.class, readerIterator.next().getClass());
        assertFalse(readerIterator.hasNext());
    }


    private <T> void _testCollectionsCommon(final String testName, final Collection<T> collection, final T element)
            throws Exception {

        // Not null.
        assertNotNull(collection, testName + " - returned collection is null.");

        // Immutability.
        try {
            collection.add(element);
            fail(testName + " - returned collection should be immutable.");
        } catch (final Exception e) {
            // OK.
        }
    }

    public static final class CustomReaderA implements ReaderInterceptor {

        @Override
        public Object aroundReadFrom(final ReaderInterceptorContext context) throws IOException, WebApplicationException {
            return null;
        }
    }

    public static final class CustomReaderB implements ReaderInterceptor {

        @Override
        public Object aroundReadFrom(final ReaderInterceptorContext context) throws IOException, WebApplicationException {
            return null;
        }
    }

    public static final class SimpleFeatureA implements Feature {

        private boolean initB;

        public SimpleFeatureA() {
        }

        public SimpleFeatureA(final boolean initB) {
            this.initB = initB;
        }

        @Override
        public boolean configure(final FeatureContext config) {
            config.register(initB ? CustomReaderB.class : CustomReaderA.class);
            return true;
        }
    }

    public static final class SimpleFeatureB implements Feature {

        @Override
        public boolean configure(final FeatureContext config) {
            config.register(CustomReaderB.class);
            return true;
        }
    }

    public static final class InstanceFeatureA implements Feature {

        private boolean initB;

        public InstanceFeatureA() {
        }

        public InstanceFeatureA(final boolean initB) {
            this.initB = initB;
        }

        @Override
        public boolean configure(final FeatureContext config) {
            config.register(initB ? new CustomReaderB() : new CustomReaderA());
            return true;
        }
    }

    public static final class ComplexFeature implements Feature {

        @Override
        public boolean configure(final FeatureContext config) {
            config.register(SimpleFeatureA.class);
            config.register(SimpleFeatureB.class);
            return true;
        }
    }

    public static final class RecursiveFeature implements Feature {

        @Override
        public boolean configure(final FeatureContext config) {
            config.register(new CustomReaderA());
            config.register(RecursiveFeature.class);
            return true;
        }
    }

    public static final class RecursiveInstanceFeature implements Feature {

        @Override
        public boolean configure(final FeatureContext config) {
            config.register(new CustomReaderA());
            config.register(new RecursiveInstanceFeature());
            return true;
        }
    }

    @Test
    public void testConfigureFeatureHierarchy() throws Exception {
        config.register(ComplexFeature.class);

        InjectionManager injectionManager = Injections.createInjectionManager();
        ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.configureMetaProviders(injectionManager, finalizer);

        assertTrue(config.getConfiguration().isEnabled(ComplexFeature.class));

        assertTrue(config.getConfiguration().isRegistered(CustomReaderA.class));
        assertTrue(config.getConfiguration().isRegistered(CustomReaderB.class));
    }

    @Test
    public void testConfigureFeatureRecursive() throws Exception {
        config.register(RecursiveFeature.class);
        InjectionManager injectionManager = Injections.createInjectionManager();
        ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.configureMetaProviders(injectionManager, finalizer);

        assertTrue(config.getConfiguration().isEnabled(RecursiveFeature.class));
        assertEquals(1, config.getInstances().size());
        assertSame(CustomReaderA.class, config.getInstances().iterator().next().getClass());
    }

    @Test
    public void testConfigureFeatureInstances() throws Exception {
        final SimpleFeatureA f1 = new SimpleFeatureA();
        config.register(f1);
        final SimpleFeatureA f2 = new SimpleFeatureA(true);
        config.register(f2);

        InjectionManager injectionManager = Injections.createInjectionManager();
        ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.configureMetaProviders(injectionManager, finalizer);

        assertTrue(config.getConfiguration().isEnabled(f1));
        assertFalse(config.getConfiguration().isEnabled(f2));

        assertTrue(config.getConfiguration().isRegistered(CustomReaderA.class));
        assertFalse(config.getConfiguration().isRegistered(CustomReaderB.class));
    }

    @Test
    public void testConfigureFeatureInstancesProviderInstances() throws Exception {
        final InstanceFeatureA f1 = new InstanceFeatureA();
        config.register(f1);
        final InstanceFeatureA f2 = new InstanceFeatureA(true);
        config.register(f2);

        InjectionManager injectionManager = Injections.createInjectionManager();
        ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.configureMetaProviders(injectionManager, finalizer);

        assertTrue(config.getConfiguration().isEnabled(f1));
        assertFalse(config.getConfiguration().isEnabled(f2));

        final Set<Object> providerInstances = config.getInstances();
        assertEquals(2, providerInstances.size());

        final Set<Object> pureProviderInstances =
                config.getComponentBag().getInstances(ComponentBag.excludeMetaProviders(injectionManager));
        assertEquals(1, pureProviderInstances.size());

        int a = 0;
        int b = 0;
        for (final Object instance : pureProviderInstances) {
            if (instance instanceof CustomReaderA) {
                a++;
            } else {
                b++;
            }
        }
        assertEquals(1, a);
        assertEquals(0, b);
    }

    @Test
    public void testConfigureFeatureInstanceRecursive() throws Exception {
        config.register(new RecursiveInstanceFeature());
        InjectionManager injectionManager = Injections.createInjectionManager();
        ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.configureMetaProviders(injectionManager, finalizer);
        assertEquals(0, config.getClasses().size());
        assertEquals(2, config.getInstances().size());
        final Set<Object> pureProviders =
                config.getComponentBag().getInstances(ComponentBag.excludeMetaProviders(injectionManager));
        assertEquals(1, pureProviders.size());
        assertSame(CustomReaderA.class, pureProviders.iterator().next().getClass());
    }

    public static interface Contract {
    }

    public static class Service implements Contract {
    }

    public static class ContractBinder extends AbstractBinder {

        @Override
        protected void configure() {
            bind(Service.class).to(Contract.class);
        }
    }

    public static class ContractBinderFeature implements Feature {

        @Override
        public boolean configure(final FeatureContext context) {
            context.register(new ContractBinder());
            return true;
        }
    }

    @Test
    public void testBinderConfiguringFeature() throws Exception {
        config.register(ContractBinderFeature.class);
        InjectionManager injectionManager = Injections.createInjectionManager();
        ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.configureMetaProviders(injectionManager, finalizer);
        injectionManager.completeRegistration();

        assertTrue(config.isEnabled(ContractBinderFeature.class));
        assertEquals(1, config.getInstances().size());
        assertSame(ContractBinder.class, config.getInstances().iterator().next().getClass());

        final Contract service = injectionManager.getInstance(Contract.class);
        assertNotNull(service);
        assertSame(Service.class, service.getClass());
    }

    public static class InjectMe {
    }

    public static class InjectIntoFeatureInstance implements Feature {

        @Inject
        private InjectMe injectMe;

        @Override
        public boolean configure(final FeatureContext context) {
            context.property("instance-injected", injectMe != null);
            return true;
        }
    }

    public static class InjectIntoFeatureClass implements Feature {

        @Inject
        private InjectMe injectMe;

        @Override
        public boolean configure(final FeatureContext context) {
            context.property("class-injected", injectMe != null);
            return true;
        }
    }

    public static class BindInjectMeInFeature implements Feature {
        @Override
        public boolean configure(FeatureContext context) {
            context.register(new AbstractBinder() {
                @Override
                protected void configure() {
                    bind(new InjectMe());
                }
            });
            return true;
        }
    }

    @Test
    public void testFeatureInjections() throws Exception {
        Assumptions.assumeTrue(Hk2InjectionManagerFactory.isImmediateStrategy());

        config.register(InjectIntoFeatureClass.class)
                .register(new InjectIntoFeatureInstance())
                .register(new AbstractBinder() {
                    @Override
                    protected void configure() {
                        bind(new InjectMe());
                    }
                });

        InjectionManager injectionManager = Injections.createInjectionManager();
        ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.configureMetaProviders(injectionManager, finalizer);

        assertThat("Feature instance not injected", config.getProperty("instance-injected").toString(), is("true"));
        assertThat("Feature class not injected", config.getProperty("class-injected").toString(), is("true"));
    }

    @Test
    @Disabled
    public void testFeatureInjectionsBindInFeature() throws Exception {
        config.register(new BindInjectMeInFeature());
        config.register(InjectIntoFeatureClass.class);
        config.register(new InjectIntoFeatureInstance());

        InjectionManager injectionManager = Injections.createInjectionManager();
        ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.configureMetaProviders(injectionManager, finalizer);

        assertThat("Feature instance not injected", config.getProperty("instance-injected").toString(), is("true"));
        assertThat("Feature class not injected", config.getProperty("class-injected").toString(), is("true"));
    }

    // ===========================================================================================================================

    @Test
    public void testFeatureBindingPriority() {
        final InjectionManager injectionManager = Injections.createInjectionManager();
        final ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.register(new OrderedFeature(Priorities.USER){}, Priorities.USER);
        config.register(new OrderedFeature(Priorities.USER - 100){}, Priorities.USER - 100);
        config.register(new OrderedFeature(Priorities.USER + 100){}, Priorities.USER + 100);
        config.configureMetaProviders(injectionManager, finalizer);
        int value = (int) config.getProperty(OrderedFeature.PROPERTY_NAME);

        assertEquals(Priorities.USER + 100, value);
    }

    private static class OrderedFeature implements Feature {
        private final int orderId;
        private static final String PROPERTY_NAME = "ORDER_ID";

        private OrderedFeature(int orderId) {
            this.orderId = orderId;
        }

        @Override
        public boolean configure(FeatureContext context) {
            Integer previousId = (Integer) context.getConfiguration().getProperty(PROPERTY_NAME);
            if (previousId != null) {
                assertThat(previousId, lessThan(orderId));
            }
            context.property(PROPERTY_NAME, orderId);
            return false;
        }
    }

    @Test
    public void testFeatureAnnotatedPriority() {
        final InjectionManager injectionManager = Injections.createInjectionManager();
        final ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.register(PriorityFeature1.class);
        config.register(PriorityFeature2.class);
        config.register(PriorityFeature3.class);
        config.configureMetaProviders(injectionManager, finalizer);
        int value = (int) config.getProperty(OrderedFeature.PROPERTY_NAME);

        assertEquals(Priorities.USER + 100, value);
    }

    @Priority(Priorities.USER)
    private static class PriorityFeature1 extends OrderedFeature {
        private PriorityFeature1() {
            super(Priorities.USER);
        }
    }

    @Priority(Priorities.USER - 100)
    private static class PriorityFeature2 extends OrderedFeature {
        private PriorityFeature2() {
            super(Priorities.USER - 100);
        }
    }

    @Priority(Priorities.USER + 100)
    private static class PriorityFeature3 extends OrderedFeature {
        private PriorityFeature3() {
            super(Priorities.USER + 100);
        }
    }

    // Binder ordering -------------------------------------------------------------------------

    @Test
    public void testBinderOrderingInGetClasses() {
        config.register(ContractBinderOne.class).register(ContractBinderTwo.class).register(ContractBinder.class);

        InjectionManager injectionManager = Injections.createInjectionManager();
        ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.configureMetaProviders(injectionManager, finalizer);

        Object[] classes = config.getComponentBag().getClasses(contractProvider -> true).toArray();
        Assertions.assertEquals(classes[0], ContractBinderOne.class);
        Assertions.assertEquals(classes[1], ContractBinderTwo.class);
        Assertions.assertEquals(classes[2], ContractBinder.class);
    }

    @Test
    public void testBinderOrderingInGetInstances() {
        ContractBinder[] binders = {new ContractBinderOne(), new ContractBinderTwo(), new ContractBinder()};
        config.register(binders[2]).register(binders[1]).register(binders[0]);

        InjectionManager injectionManager = Injections.createInjectionManager();
        ManagedObjectsFinalizer finalizer = new ManagedObjectsFinalizer(injectionManager);
        config.configureMetaProviders(injectionManager, finalizer);

        Object[] instances = config.getComponentBag().getInstances(contractProvider -> true).toArray();
        Assertions.assertEquals(instances[0], binders[2]);
        Assertions.assertEquals(instances[1], binders[1]);
        Assertions.assertEquals(instances[2], binders[0]);
    }

    public static class ContractBinderOne extends ContractBinder {
    }

    public static class ContractBinderTwo extends ContractBinder {
    }
}