WrappingJerseyInjectionTarget.java

/*
 * Copyright (c) 2021 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.injector;

import java.util.Collection;
import java.util.Collections;
import java.util.Set;

import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.InjectionException;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.InjectionPoint;
import javax.enterprise.inject.spi.InjectionTarget;

import org.glassfish.jersey.internal.inject.InjectionResolver;
import org.glassfish.jersey.internal.util.collection.LazyValue;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;

/**
 * An implementation of {@link InjectionTarget} that just wraps the provided {@code InjectionTarget} because of additional
 * features in an injection phase.
 *
 * @author Petr Bouda
 */
public class WrappingJerseyInjectionTarget<T> extends AbstractInjectionTarget<T> {

    private static InjectionTarget NOOP_INJECTION_TARGET = new NoOpInjectionTarget();

    private final Bean<T> bean;
    private final LazyValue<JerseyInstanceInjector<T>> injector;
    private final InjectionTarget<T> delegate;
    private Collection<InjectionResolver> resolvers;

    /**
     * Creates a new jersey injection target with delegate as a {@link NoOpInjectionTarget} that creates no operation that
     * means that only jersey injection is available as a additional feature.
     *
     * @param bean      bean as descriptor of the class which will be injected.
     * @param resolvers all resolvers that can provide a valued for Jersey-specific injection.
     */
    public WrappingJerseyInjectionTarget(Bean<T> bean, Collection<InjectionResolver> resolvers) {
        this(NOOP_INJECTION_TARGET, bean, resolvers);
    }

    /**
     * An implementation of {@link InjectionTarget} for classes that do not fulfill bean class requirements
     * (e.g. are abstract or non-static inner classes). Instances of these class can be injected using this implementation. If the
     * application attempts to {@link #produce(CreationalContext)} a new instance of the class, {@code CreationException} is
     * thrown.
     *
     * @param delegate  CDI specific injection target.
     * @param bean      bean as descriptor of the class which will be injected.
     * @param resolvers all resolvers that can provide a valued for Jersey-specific injection.
     */
    public WrappingJerseyInjectionTarget(InjectionTarget<T> delegate, Bean<T> bean, Collection<InjectionResolver> resolvers) {
        this.bean = bean;
        this.delegate = delegate;
        this.resolvers = resolvers;
        this.injector = Values.lazy((Value<JerseyInstanceInjector<T>>)
                () -> new JerseyInstanceInjector<>(bean, this.resolvers));
    }

    @Override
    public void inject(T instance, CreationalContext<T> ctx) {
        /*
         * If an instance contains any fields which be injected by Jersey then Jersey attempts to inject them using annotations
         * retrieves from registered InjectionResolvers.
         */
        try {
            injector.get().inject(instance);
        } catch (Throwable cause) {
            throw new InjectionException(
                    "Exception occurred during Jersey/JAX-RS annotations processing in the class: " + bean.getBeanClass(), cause);
        }

        /*
         * The rest of the fields (annotated by @Inject) are injected using CDI.
         */
        super.inject(instance, ctx);
    }

    @Override
    InjectionTarget<T> delegate() {
        return delegate;
    }

    private static class NoOpInjectionTarget implements InjectionTarget<Object> {

        @Override
        public void inject(Object instance, CreationalContext<Object> ctx) {
        }

        @Override
        public void postConstruct(Object instance) {
        }

        @Override
        public void preDestroy(Object instance) {
        }

        @Override
        public Object produce(CreationalContext<Object> ctx) {
            return null;
        }

        @Override
        public void dispose(Object instance) {
        }

        @Override
        public Set<InjectionPoint> getInjectionPoints() {
            return Collections.emptySet();
        }
    }
}