All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.microprofile.testing.junit5.HelidonJunitExtension Maven / Gradle / Ivy

/*
 * Copyright (c) 2020, 2024 Oracle and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.helidon.microprofile.testing.junit5;

import java.io.IOException;
import java.io.Serial;
import java.io.StringReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import io.helidon.config.mp.MpConfigSources;
import io.helidon.microprofile.server.JaxRsCdiExtension;
import io.helidon.microprofile.server.ServerCdiExtension;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.se.SeContainer;
import jakarta.enterprise.inject.se.SeContainerInitializer;
import jakarta.enterprise.inject.spi.AfterBeanDiscovery;
import jakarta.enterprise.inject.spi.BeforeBeanDiscovery;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.enterprise.inject.spi.Extension;
import jakarta.enterprise.inject.spi.InjectionPoint;
import jakarta.enterprise.inject.spi.ProcessInjectionPoint;
import jakarta.enterprise.inject.spi.configurator.AnnotatedTypeConfigurator;
import jakarta.enterprise.util.AnnotationLiteral;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.spi.ConfigBuilder;
import org.eclipse.microprofile.config.spi.ConfigProviderResolver;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.glassfish.jersey.ext.cdi1x.internal.CdiComponentProvider;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.InvocationInterceptor;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;


/**
 * Junit5 extension to support Helidon CDI container in tests.
 */
class HelidonJunitExtension implements BeforeAllCallback,
                                       AfterAllCallback,
                                       BeforeEachCallback,
                                       AfterEachCallback,
                                       InvocationInterceptor,
                                       ParameterResolver {
    private static final Set> HELIDON_TEST_ANNOTATIONS =
            Set.of(AddBean.class, AddConfig.class, AddExtension.class, Configuration.class, AddJaxRs.class);
    private static final Map, Annotation> BEAN_DEFINING = new HashMap<>();

    static {
        BEAN_DEFINING.put(ApplicationScoped.class, ApplicationScoped.Literal.INSTANCE);
        BEAN_DEFINING.put(Singleton.class, ApplicationScoped.Literal.INSTANCE);
        BEAN_DEFINING.put(RequestScoped.class, RequestScoped.Literal.INSTANCE);
        BEAN_DEFINING.put(Dependent.class, Dependent.Literal.INSTANCE);
    }

    private final List classLevelExtensions = new ArrayList<>();
    private final List classLevelBeans = new ArrayList<>();
    private final ConfigMeta classLevelConfigMeta = new ConfigMeta();
    private boolean classLevelDisableDiscovery = false;
    private boolean resetPerTest;

    private Class testClass;
    private ConfigProviderResolver configProviderResolver;
    private Config config;
    private SeContainer container;


    @SuppressWarnings("unchecked")
    @Override
    public void beforeAll(ExtensionContext context) {
        testClass = context.getRequiredTestClass();

        AddConfig[] configs = getAnnotations(testClass, AddConfig.class);
        classLevelConfigMeta.addConfig(configs);
        classLevelConfigMeta.configuration(testClass.getAnnotation(Configuration.class));
        classLevelConfigMeta.addConfigBlock(testClass.getAnnotation(AddConfigBlock.class));
        configProviderResolver = ConfigProviderResolver.instance();

        AddExtension[] extensions = getAnnotations(testClass, AddExtension.class);
        classLevelExtensions.addAll(Arrays.asList(extensions));

        AddBean[] beans = getAnnotations(testClass, AddBean.class);
        classLevelBeans.addAll(Arrays.asList(beans));

        HelidonTest testAnnot = testClass.getAnnotation(HelidonTest.class);
        if (testAnnot != null) {
            resetPerTest = testAnnot.resetPerTest();
        }

        DisableDiscovery discovery = testClass.getAnnotation(DisableDiscovery.class);
        if (discovery != null) {
            classLevelDisableDiscovery = discovery.value();
        }

        if (resetPerTest) {
            validatePerTest();

            return;
        }
        validatePerClass();

        // add beans when using JaxRS
        AddJaxRs addJaxRsAnnotation = testClass.getAnnotation(AddJaxRs.class);
        if (addJaxRsAnnotation != null){
            classLevelExtensions.add(ProcessAllAnnotatedTypesLiteral.INSTANCE);
            classLevelExtensions.add(ServerCdiExtensionLiteral.INSTANCE);
            classLevelExtensions.add(JaxRsCdiExtensionLiteral.INSTANCE);
            classLevelExtensions.add(CdiComponentProviderLiteral.INSTANCE);
            classLevelBeans.add(WeldRequestScopeLiteral.INSTANCE);
        }

        configure(classLevelConfigMeta);

        if (!classLevelConfigMeta.useExisting) {
            // the container startup is delayed in case we `useExisting`, so the is first set up by the user
            // when we do not need to `useExisting`, we want to start early, so parameterized test method sources that use CDI
            // can work
            startContainer(classLevelBeans, classLevelExtensions, classLevelDisableDiscovery);
        }
    }

    @SuppressWarnings("unchecked")
    private  T[] getAnnotations(Class testClass, Class annotClass) {
        // inherited does not help, as it only returns annot from superclass if
        // child has none
        T[] directAnnotations = testClass.getAnnotationsByType(annotClass);

        List allAnnotations = new ArrayList<>(List.of(directAnnotations));

        Class superClass = testClass.getSuperclass();
        while (superClass != null) {
            directAnnotations = superClass.getAnnotationsByType(annotClass);
            allAnnotations.addAll(List.of(directAnnotations));
            superClass = superClass.getSuperclass();
        }

        Object result = Array.newInstance(annotClass, allAnnotations.size());
        for (int i = 0; i < allAnnotations.size(); i++) {
             Array.set(result, i, allAnnotations.get(i));
        }

        return (T[]) result;
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        if (resetPerTest) {
            Method method = context.getRequiredTestMethod();
            AddConfig[] configs = method.getAnnotationsByType(AddConfig.class);
            ConfigMeta methodLevelConfigMeta = classLevelConfigMeta.nextMethod();
            methodLevelConfigMeta.addConfig(configs);
            methodLevelConfigMeta.configuration(method.getAnnotation(Configuration.class));
            methodLevelConfigMeta.addConfigBlock(method.getAnnotation(AddConfigBlock.class));

            configure(methodLevelConfigMeta);

            List methodLevelExtensions = new ArrayList<>(classLevelExtensions);
            List methodLevelBeans = new ArrayList<>(classLevelBeans);
            boolean methodLevelDisableDiscovery = classLevelDisableDiscovery;

            AddExtension[] extensions = method.getAnnotationsByType(AddExtension.class);
            methodLevelExtensions.addAll(Arrays.asList(extensions));

            AddBean[] beans = method.getAnnotationsByType(AddBean.class);
            methodLevelBeans.addAll(Arrays.asList(beans));

            DisableDiscovery discovery = method.getAnnotation(DisableDiscovery.class);
            if (discovery != null) {
                methodLevelDisableDiscovery = discovery.value();
            }

            startContainer(methodLevelBeans, methodLevelExtensions, methodLevelDisableDiscovery);
        }
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        if (resetPerTest) {
            releaseConfig();
            stopContainer();
        }
    }

    private void validatePerClass() {
        Method[] methods = testClass.getMethods();
        for (Method method : methods) {
            if (method.getAnnotation(Test.class) != null) {
                // a test method
                if (hasHelidonTestAnnotation(method)) {
                    throw new RuntimeException("When a class is annotated with @HelidonTest, "
                                                       + "there is a single CDI container used to invoke all "
                                                       + "test methods on the class. Method " + method
                                                       + " has an annotation that modifies container behavior.");
                }
            }
        }

        methods = testClass.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getAnnotation(Test.class) != null) {
                // a test method
                if (hasHelidonTestAnnotation(method)) {
                    throw new RuntimeException("When a class is annotated with @HelidonTest, "
                                                       + "there is a single CDI container used to invoke all "
                                                       + "test methods on the class. Method " + method
                                                       + " has an annotation that modifies container behavior.");
                }
            }
        }

        AddJaxRs addJaxRsAnnotation = testClass.getAnnotation(AddJaxRs.class);
        if (addJaxRsAnnotation != null){
            if (testClass.getAnnotation(DisableDiscovery.class) == null){
                throw new RuntimeException("@AddJaxRs annotation should be used only with @DisableDiscovery annotation.");
            }
        }
    }

    private boolean hasHelidonTestAnnotation(AnnotatedElement element) {
        for (Class aClass : HELIDON_TEST_ANNOTATIONS) {
            if (element.getAnnotation(aClass) != null) {
                return true;
            }
        }
        return false;
    }

    private void validatePerTest() {
        Constructor[] constructors = testClass.getConstructors();
        if (constructors.length > 1) {
            throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true),"
                                               + " the class must have only a single no-arg constructor");
        }
        if (constructors.length == 1) {
            Constructor c = constructors[0];
            if (c.getParameterCount() > 0) {
                throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true),"
                                                   + " the class must have a no-arg constructor");
            }
        }

        Field[] fields = testClass.getFields();
        for (Field field : fields) {
            if (field.getAnnotation(Inject.class) != null) {
                throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true),"
                                                   + " injection into fields or constructor is not supported, as each"
                                                   + " test method uses a different CDI container. Field " + field
                                                   + " is annotated with @Inject");
            }
        }

        fields = testClass.getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(Inject.class) != null) {
                throw new RuntimeException("When a class is annotated with @HelidonTest(resetPerTest=true),"
                                                   + " injection into fields or constructor is not supported, as each"
                                                   + " test method uses a different CDI container. Field " + field
                                                   + " is annotated with @Inject");
            }
        }
    }

    private void configure(ConfigMeta configMeta) {
        if (config != null) {
            configProviderResolver.releaseConfig(config);
        }
        if (!configMeta.useExisting) {
            // only create a custom configuration if not provided by test method/class
            // prepare configuration
            ConfigBuilder builder = configProviderResolver.getBuilder();

            configMeta.additionalSources.forEach(it -> {
                String fileName = it.trim();
                int idx = fileName.lastIndexOf('.');
                String type = idx > -1 ? fileName.substring(idx + 1) : "properties";
                try {
                    Enumeration urls = Thread.currentThread().getContextClassLoader().getResources(fileName);
                    urls.asIterator().forEachRemaining(url -> builder.withSources(MpConfigSources.create(type, url)));
                } catch (IOException e) {
                    throw new IllegalStateException("Failed to read \"" + fileName + "\" from classpath", e);
                }
            });
            if (configMeta.type != null && configMeta.block != null) {
                builder.withSources(MpConfigSources.create(configMeta.type, new StringReader(configMeta.block)));
            }
            config = builder
                    .withSources(MpConfigSources.create(configMeta.additionalKeys))
                    .addDefaultSources()
                    .addDiscoveredSources()
                    .addDiscoveredConverters()
                    .build();
            configProviderResolver.registerConfig(config, Thread.currentThread().getContextClassLoader());
        }
    }
    private void releaseConfig() {
        if (configProviderResolver != null && config != null) {
            configProviderResolver.releaseConfig(config);
            config = null;
        }
    }

    @SuppressWarnings("unchecked")
    private void startContainer(List beanAnnotations,
                                List extensionAnnotations,
                                boolean disableDiscovery) {

        // now let's prepare the CDI bootstrapping
        SeContainerInitializer initializer = SeContainerInitializer.newInstance();

        if (disableDiscovery) {
            initializer.disableDiscovery();
        }

        initializer.addExtensions(new AddBeansExtension(testClass, beanAnnotations));

        for (AddExtension addExtension : extensionAnnotations) {
            Class extensionClass = addExtension.value();
            if (Modifier.isPublic(extensionClass.getModifiers())) {
                initializer.addExtensions(addExtension.value());
            } else {
                throw new IllegalArgumentException("Extension classes must be public, but " + extensionClass
                        .getName() + " is not");
            }
        }

        container = initializer.initialize();
    }

    private void stopContainer() {
        if (container != null) {
            container.close();
            container = null;
        }
    }

    @Override
    public void afterAll(ExtensionContext context) {
        stopContainer();
        releaseConfig();
        callAfterStop();
    }

    @Override
    public  T interceptTestClassConstructor(Invocation invocation,
                                               ReflectiveInvocationContext> invocationContext,
                                               ExtensionContext extensionContext) throws Throwable {

        if (resetPerTest) {
            // Junit creates test instance
            return invocation.proceed();
        }

        // we need to start container before the test class is instantiated, to honor @BeforeAll that
        // creates a custom MP config
        if (container == null) {
            // at this early stage the class should be checked whether it is annotated with
            // @TestInstance(TestInstance.Lifecycle.PER_CLASS) to start correctly the container
            TestInstance testClassAnnotation = testClass.getAnnotation(TestInstance.class);
            if (testClassAnnotation != null && testClassAnnotation.value().equals(TestInstance.Lifecycle.PER_CLASS)){
                throw new RuntimeException("When a class is annotated with @HelidonTest, "
                        + "it is not compatible with @TestInstance(TestInstance.Lifecycle.PER_CLASS)"
                        + "annotation, as it is a Singleton CDI Bean.");
            }
            startContainer(classLevelBeans, classLevelExtensions, classLevelDisableDiscovery);
        }

        // we need to replace instantiation with CDI lookup, to properly injection into fields (and constructors)
        invocation.skip();

        return container.select(invocationContext.getExecutable().getDeclaringClass())
                .get();
    }

    @Override
    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
            throws ParameterResolutionException {

        Executable executable = parameterContext.getParameter().getDeclaringExecutable();

        if (resetPerTest) {
            if (executable instanceof Constructor) {
                throw new ParameterResolutionException(
                        "When a test class is annotated with @HelidonTest(resetPerMethod=true), constructor must not have "
                                + "parameters.");
            }
        } else {
            // we need to start container before the test class is instantiated, to honor @BeforeAll that
            // creates a custom MP config
            if (container == null) {
                startContainer(classLevelBeans, classLevelExtensions, classLevelDisableDiscovery);
            }
        }

        Class paramType = parameterContext.getParameter().getType();

        if (executable instanceof Constructor) {
            return !container.select(paramType).isUnsatisfied();
        } else if (executable instanceof Method) {
            if (paramType.equals(SeContainer.class)) {
                return true;
            }
            if (paramType.equals(WebTarget.class)) {
                return true;
            }
        }

        return false;
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
            throws ParameterResolutionException {
        Executable executable = parameterContext.getParameter().getDeclaringExecutable();
        Class paramType = parameterContext.getParameter().getType();

        if (executable instanceof Method) {
            if (paramType.equals(SeContainer.class)) {
                return container;
            }
            if (paramType.equals(WebTarget.class)) {
                return container.select(WebTarget.class).get();
            }
        }
        // we return null, as construction of the object is done by CDI
        // for primitive types we must return appropriate primitive default
        if (paramType.isPrimitive()) {
            // a hack to get to default value of a primitive type
            return Array.get(Array.newInstance(paramType, 1), 0);
        } else {
            return null;
        }
    }

    private void callAfterStop() {
        List toInvoke = new ArrayList<>();

        Method[] methods = testClass.getMethods();
        for (Method method : methods) {
            AfterStop annotation = method.getAnnotation(AfterStop.class);
            if (annotation != null) {
                if (method.getParameterCount() != 0) {
                    throw new IllegalStateException("Method " + method + " is annotated with @AfterStop, but it has parameters");
                }
                if (Modifier.isStatic(method.getModifiers())) {
                    method.setAccessible(true);
                    toInvoke.add(method);
                } else {
                    throw new IllegalStateException("Method " + method + " is annotated with @AfterStop, but it is not static");
                }
            }
        }

        for (Method method : toInvoke) {
            try {
                method.invoke(testClass);
            } catch (Exception e) {
                throw new IllegalStateException("Failed to invoke method: " + method, e);
            }
        }
    }

    // this is not registered as a bean - we manually register an instance
    @SuppressWarnings("CdiManagedBeanInconsistencyInspection")
    private static class AddBeansExtension implements Extension {
        private final Class testClass;
        private final List addBeans;

        private final HashMap socketAnnotations = new HashMap<>();

        private AddBeansExtension(Class testClass, List addBeans) {
            this.testClass = testClass;
            this.addBeans = addBeans;
        }


        void processSocketInjectionPoints(@Observes ProcessInjectionPoint event) throws Exception{
             InjectionPoint injectionPoint = event.getInjectionPoint();
             Set qualifiers = injectionPoint.getQualifiers();
                for (Annotation qualifier : qualifiers) {
                    if (qualifier.annotationType().equals(Socket.class)) {
                        String value = ((Socket) qualifier).value();
                        socketAnnotations.put(value, qualifier);
                        break;
                    }
                }

        }

        void registerOtherBeans(@Observes AfterBeanDiscovery event) {

            Client client = ClientBuilder.newClient();

            //register for all named Ports
            socketAnnotations.forEach((namedPort, qualifier) -> {

                event.addBean()
                        .addType(WebTarget.class)
                        .scope(ApplicationScoped.class)
                        .qualifiers(qualifier)
                        .createWith(context -> getWebTarget(client, namedPort));
            });

            event.addBean()
                    .addType(jakarta.ws.rs.client.WebTarget.class)
                    .scope(ApplicationScoped.class)
                    .createWith(context -> getWebTarget(client, "@default"));

        }

        @SuppressWarnings("unchecked")
        private static WebTarget getWebTarget(Client client, String namedPort) {
            try {
                Class extClass = (Class) Class
                        .forName("io.helidon.microprofile.server.ServerCdiExtension");
                Extension extension = CDI.current().getBeanManager().getExtension(extClass);
                Method m = extension.getClass().getMethod("port", String.class);
                int port = (int) m.invoke(extension, new Object[]{namedPort});
                String uri = "http://localhost:" + port;
                return client.target(uri);
            } catch (ReflectiveOperationException e) {
                return client.target("http://localhost:7001");
            }
        }

        void registerAddedBeans(@Observes BeforeBeanDiscovery event) {
            event.addAnnotatedType(testClass, "junit-" + testClass.getName())
                    .add(ApplicationScoped.Literal.INSTANCE);

            for (AddBean addBean : addBeans) {
                Annotation scope;
                Class definedScope = addBean.scope();

                scope = BEAN_DEFINING.get(definedScope);

                if (scope == null) {
                    throw new IllegalStateException(
                            "Only on of " + BEAN_DEFINING.keySet() + " scopes are allowed in tests. Scope "
                                    + definedScope.getName() + " is not allowed for bean " + addBean.value().getName());
                }

                AnnotatedTypeConfigurator configurator = event
                        .addAnnotatedType(addBean.value(), "junit-" + addBean.value().getName());
                if (!hasBda(addBean.value())) {
                    configurator.add(scope);
                }
            }
        }

        private boolean hasBda(Class value) {
            // does it have bean defining annotation?
            for (Class aClass : BEAN_DEFINING.keySet()) {
                if (value.getAnnotation(aClass) != null) {
                    return true;
                }
            }

            return false;
        }

    }

    private static final class ConfigMeta {
        private final Map additionalKeys = new HashMap<>();
        private final List additionalSources = new ArrayList<>();
        private String type;
        private String block;
        private boolean useExisting;
        private String profile;

        private ConfigMeta() {
            // to allow SeContainerInitializer (forbidden by default because of native image)
            additionalKeys.put("mp.initializer.allow", "true");
            additionalKeys.put("mp.initializer.no-warn", "true");
            // to run on random port
            additionalKeys.put("server.port", "0");
            // higher ordinal then all the defaults, system props and environment variables
            additionalKeys.putIfAbsent(ConfigSource.CONFIG_ORDINAL, "1000");
            // profile
            additionalKeys.put("mp.config.profile", "test");
        }

        private void addConfig(AddConfig[] configs) {
            for (AddConfig config : configs) {
                additionalKeys.put(config.key(), config.value());
            }
        }

        private void configuration(Configuration config) {
            if (config == null) {
                return;
            }
            useExisting = config.useExisting();
            profile = config.profile();
            additionalSources.addAll(List.of(config.configSources()));
            //set additional key for profile
            additionalKeys.put("mp.config.profile", profile);
        }

        private void addConfigBlock(AddConfigBlock config) {
            if (config == null) {
                return;
            }
            this.type = config.type();
            this.block = config.value();
        }

        ConfigMeta nextMethod() {
            ConfigMeta methodMeta = new ConfigMeta();

            methodMeta.additionalKeys.putAll(this.additionalKeys);
            methodMeta.additionalSources.addAll(this.additionalSources);
            methodMeta.useExisting = this.useExisting;
            methodMeta.profile = this.profile;

            return methodMeta;
        }
    }


    /**
     * Add WeldRequestScope. Used with {@code AddJaxRs}.
     */
    private static final class WeldRequestScopeLiteral extends AnnotationLiteral implements AddBean {

        static final WeldRequestScopeLiteral INSTANCE = new WeldRequestScopeLiteral();

        @Serial
        private static final long serialVersionUID = 1L;

        @Override
        public Class value() {
            return org.glassfish.jersey.weld.se.WeldRequestScope.class;
        }

        @Override
        public Class scope() {
            return RequestScoped.class;
        }
    }


    /**
     * Add ProcessAllAnnotatedTypes. Used with {@code AddJaxRs}.
     */
    private static final class ProcessAllAnnotatedTypesLiteral extends AnnotationLiteral implements AddExtension {

        static final ProcessAllAnnotatedTypesLiteral INSTANCE = new ProcessAllAnnotatedTypesLiteral();

        @Serial
        private static final long serialVersionUID = 1L;

        @Override
        public Class value() {
            return org.glassfish.jersey.ext.cdi1x.internal.ProcessAllAnnotatedTypes.class;
        }
    }

    /**
     * Add ServerCdiExtension. Used with {@code AddJaxRs}.
     */
    private static final class ServerCdiExtensionLiteral extends AnnotationLiteral implements AddExtension {

        static final ServerCdiExtensionLiteral INSTANCE = new ServerCdiExtensionLiteral();

        @Serial
        private static final long serialVersionUID = 1L;

        @Override
        public Class value() {
            return ServerCdiExtension.class;
        }
    }

    /**
     * Add WeldRequestScope. Used with {@code AddJaxRs}.
     */
    private static final class JaxRsCdiExtensionLiteral extends AnnotationLiteral implements AddExtension {

        static final JaxRsCdiExtensionLiteral INSTANCE = new JaxRsCdiExtensionLiteral();

        @Serial
        private static final long serialVersionUID = 1L;

        @Override
        public Class value() {
            return JaxRsCdiExtension.class;
        }
    }

    /**
     * Add CdiComponentProvider. Used with {@code AddJaxRs}.
     */
    private static final class CdiComponentProviderLiteral extends AnnotationLiteral implements AddExtension {

        static final CdiComponentProviderLiteral INSTANCE = new CdiComponentProviderLiteral();

        @Serial
        private static final long serialVersionUID = 1L;

        @Override
        public Class value() {
            return CdiComponentProvider.class;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy