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

org.glassfish.jersey.inject.hk2.Hk2Helper Maven / Gradle / Ivy

/*
 * Copyright (c) 2017, 2018 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.hk2;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.glassfish.jersey.internal.LocalizationMessages;
import org.glassfish.jersey.internal.inject.AliasBinding;
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.DisposableSupplier;
import org.glassfish.jersey.internal.inject.InjectionResolverBinding;
import org.glassfish.jersey.internal.inject.InstanceBinding;
import org.glassfish.jersey.internal.inject.PerLookup;
import org.glassfish.jersey.internal.inject.PerThread;
import org.glassfish.jersey.internal.inject.SupplierClassBinding;
import org.glassfish.jersey.internal.inject.SupplierInstanceBinding;

import org.glassfish.hk2.api.ActiveDescriptor;
import org.glassfish.hk2.api.DynamicConfiguration;
import org.glassfish.hk2.api.DynamicConfigurationService;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.AbstractActiveDescriptor;
import org.glassfish.hk2.utilities.ActiveDescriptorBuilder;
import org.glassfish.hk2.utilities.Binder;
import org.glassfish.hk2.utilities.BuilderHelper;
import org.glassfish.hk2.utilities.ServiceLocatorUtilities;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.hk2.utilities.binding.ServiceBindingBuilder;
import org.glassfish.hk2.utilities.reflection.ParameterizedTypeImpl;

/**
 * This class contains the convenient methods for translation from jersey classes to HK2 and visa versa, then contains methods
 * for binding structures such as {@link org.glassfish.jersey.internal.inject.Binder} or {@link Binding} to a provided service
 * locator.
 */
class Hk2Helper {

    /**
     * Bind a translated Jersey-like {@link org.glassfish.jersey.internal.inject.Binder} to HK2-like {@link Binder}.
     *
     * @param injectionManager HK2 injection manager.
     * @param jerseyBinder     Jersey-like binder.
     */
    static void bind(AbstractHk2InjectionManager injectionManager, org.glassfish.jersey.internal.inject.Binder jerseyBinder) {
        bind(injectionManager.getServiceLocator(), Bindings.getBindings(injectionManager, jerseyBinder));
    }

    /**
     * Bind descriptors to Hk2-like {@link Binder}.
     *
     * @param locator HK2 locator.
     * @param binding single descriptor.
     */
    static void bind(ServiceLocator locator, Binding binding) {
        bindBinding(locator, binding);
    }

    /**
     * Bind descriptors to Hk2-like {@link Binder}.
     *
     * @param locator     HK2 locator.
     * @param descriptors collection of descriptors.
     */
    static void bind(ServiceLocator locator, Iterable descriptors) {
        DynamicConfiguration dc = getDynamicConfiguration(locator);
        for (Binding binding : descriptors) {
            bindBinding(locator, dc, binding);
        }
        dc.commit();
    }

    /**
     * Gets {@link DynamicConfigurationService} object from {@link ServiceLocator HK2 Locator} and creates a new object of
     * {@link DynamicConfiguration} to bind new services.
     *
     * @param locator HK2 locator.
     * @return new instance of {@code DynamicConfiguration} to bind new services.
     */
    private static DynamicConfiguration getDynamicConfiguration(ServiceLocator locator) {
        DynamicConfigurationService dcs = locator.getService(DynamicConfigurationService.class);
        return dcs.createDynamicConfiguration();
    }

    /**
     * Binds the single descriptor using a single {@link DynamicConfiguration}.
     *
     * @param locator HK2 injection manager.
     * @param binding Jersey descriptor as a holder of information about an injection point.
     */
    private static void bindBinding(ServiceLocator locator, Binding binding) {
        DynamicConfiguration dc = getDynamicConfiguration(locator);
        bindBinding(locator, dc, binding);
        dc.commit();
    }

    /**
     * Binds the single descriptor using an external {@link DynamicConfiguration}.
     *
     * @param locator HK2 injection manager.
     * @param dc      HK2 Dynamic configuration to bind the object.
     * @param binding Jersey descriptor as a holder of information about an injection point.
     */
    private static void bindBinding(ServiceLocator locator, DynamicConfiguration dc, Binding binding) {
        if (ClassBinding.class.isAssignableFrom(binding.getClass())) {
            ActiveDescriptor activeDescriptor = translateToActiveDescriptor((ClassBinding) binding);
            bindBinding(locator, dc, activeDescriptor, binding.getAliases());

        } else if (InstanceBinding.class.isAssignableFrom(binding.getClass())) {
            ActiveDescriptor activeDescriptor = translateToActiveDescriptor((InstanceBinding) binding);
            bindBinding(locator, dc, activeDescriptor, binding.getAliases());

        } else if (InjectionResolverBinding.class.isAssignableFrom(binding.getClass())) {
            InjectionResolverBinding resolverDescriptor = (InjectionResolverBinding) binding;
            bindBinding(locator, dc, wrapInjectionResolver(resolverDescriptor), binding.getAliases());
            bindBinding(locator, dc, translateToActiveDescriptor(resolverDescriptor), binding.getAliases());

        } else if (SupplierClassBinding.class.isAssignableFrom(binding.getClass())) {
            bindSupplierClassBinding(locator, (SupplierClassBinding) binding);

        } else if (SupplierInstanceBinding.class.isAssignableFrom(binding.getClass())) {
            bindSupplierInstanceBinding(locator, (SupplierInstanceBinding) binding);

        } else {
            throw new RuntimeException(LocalizationMessages.UNKNOWN_DESCRIPTOR_TYPE(binding.getClass().getSimpleName()));
        }
    }

    @SuppressWarnings("unchecked")
    private static ActiveDescriptor wrapInjectionResolver(InjectionResolverBinding resolverDescriptor) {
        InjectionResolverWrapper wrappedResolver = new InjectionResolverWrapper<>(resolverDescriptor.getResolver());
        return translateToActiveDescriptor(Bindings.service(wrappedResolver),
                new ParameterizedTypeImpl(InjectionResolver.class, resolverDescriptor.getResolver().getAnnotation()));
    }

    /**
     * Registers a new instance {@link Binder} using the information from the Jersey binding {@link SupplierInstanceBinding}.
     *
     * @param locator HK2 instance manager.
     * @param binding Jersey descriptor as a holder of information about an injection point.
     */
    private static void bindSupplierInstanceBinding(ServiceLocator locator, SupplierInstanceBinding binding) {
        Consumer bindConsumer = binder -> {
            Supplier supplier = binding.getSupplier();
            boolean disposable = DisposableSupplier.class.isAssignableFrom(supplier.getClass());
            // Bind the Supplier itself to be able to inject - Supplier or DisposableSupplier;
            // The contract of the supplier is not registered that means that the instance of the supplier can be retrieved
            // only using Supplier interface and not using implementation class itself. Supplier can be registered only once with
            // all provided contracts.
            AbstractActiveDescriptor> supplierBuilder = BuilderHelper.createConstantDescriptor(supplier);
            binding.getContracts().forEach(contract -> {
                supplierBuilder.addContractType(new ParameterizedTypeImpl(Supplier.class, contract));
                if (disposable) {
                    supplierBuilder.addContractType(new ParameterizedTypeImpl(DisposableSupplier.class, contract));
                }
            });
            // Always call SupplierFactoryBridge.
            supplierBuilder.setName(binding.getName());
            binding.getQualifiers().forEach(supplierBuilder::addQualifierAnnotation);
            binder.bind(supplierBuilder);

            // Register wrapper for factory functionality, wrapper automatically call service locator which is able to retrieve
            // the service in the proper context and scope. Bridge is registered for all contracts but is able to lookup from
            // service locator only using the first contract.
            ServiceBindingBuilder builder = binder.bindFactory(new InstanceSupplierFactoryBridge<>(supplier, disposable));
            setupSupplierFactoryBridge(binding, builder);
        };

        ServiceLocatorUtilities.bind(locator, createBinder(bindConsumer));
    }

    /**
     * Registers a new instance {@link Binder} using the information from the Jersey binding {@link SupplierClassBinding}.
     *
     * @param locator HK2 instance manager.
     * @param binding Jersey descriptor as a holder of information about an injection point.
     */
    private static void bindSupplierClassBinding(ServiceLocator locator, SupplierClassBinding binding) {
        Consumer bindConsumer = binder -> {
            boolean disposable = DisposableSupplier.class.isAssignableFrom(binding.getSupplierClass());

            // Bind the Supplier itself to be able to inject - Supplier supplier;
            // The contract of the supplier is not registered that means that the instance of the supplier can be retrieved
            // only using Supplier interface and not using implementation class itself. Supplier can be registered only once with
            // all provided contracts.
            ServiceBindingBuilder supplierBuilder = binder.bind(binding.getSupplierClass());
            binding.getContracts().forEach(contract -> {
                supplierBuilder.to(new ParameterizedTypeImpl(Supplier.class, contract));
                if (disposable) {
                    supplierBuilder.to(new ParameterizedTypeImpl(DisposableSupplier.class, contract));
                }
            });
            binding.getQualifiers().forEach(supplierBuilder::qualifiedBy);
            supplierBuilder.named(binding.getName());
            supplierBuilder.in(transformScope(binding.getSupplierScope()));
            binder.bind(supplierBuilder);

            // Register wrapper for factory functionality, wrapper automatically call service locator which is able to retrieve
            // the service in the proper context and scope. Bridge is registered for all contracts but is able to lookup from
            // service locator only using the first contract.
            Type contract = null;
            if (binding.getContracts().iterator().hasNext()) {
                contract = binding.getContracts().iterator().next();
            }

            ServiceBindingBuilder builder = binder.bindFactory(
                    new SupplierFactoryBridge<>(locator, contract, binding.getName(), disposable));
            setupSupplierFactoryBridge(binding, builder);
            if (binding.getImplementationType() != null) {
                builder.asType(binding.getImplementationType());
            }
        };

        ServiceLocatorUtilities.bind(locator, createBinder(bindConsumer));
    }

    private static void setupSupplierFactoryBridge(Binding binding, ServiceBindingBuilder builder) {
        builder.named(binding.getName());
        binding.getContracts().forEach(builder::to);
        binding.getQualifiers().forEach(builder::qualifiedBy);
        builder.in(transformScope(binding.getScope()));

        if (binding.getRank() != null) {
            builder.ranked(binding.getRank());
        }

        if (binding.isProxiable() != null) {
            builder.proxy(binding.isProxiable());
        }

        if (binding.isProxiedForSameScope() != null) {
            builder.proxyForSameScope(binding.isProxiedForSameScope());
        }
    }

    static ActiveDescriptor translateToActiveDescriptor(ClassBinding desc) {
        ActiveDescriptorBuilder binding = BuilderHelper.activeLink(desc.getService()).named(desc.getName())
                .analyzeWith(desc.getAnalyzer());

        if (desc.getScope() != null) {
            binding.in(transformScope(desc.getScope()));
        }

        if (desc.getRank() != null) {
            binding.ofRank(desc.getRank());
        }

        for (Annotation annotation : desc.getQualifiers()) {
            binding.qualifiedBy(annotation);
        }

        for (Type contract : desc.getContracts()) {
            binding.to(contract);
        }

        if (desc.isProxiable() != null) {
            binding.proxy(desc.isProxiable());
        }

        if (desc.isProxiedForSameScope() != null) {
            binding.proxyForSameScope(desc.isProxiedForSameScope());
        }

        if (desc.getImplementationType() != null) {
            binding.asType(desc.getImplementationType());
        }

        return binding.build();
    }

    /**
     * Binds a new instance {@link Binding} using the information from the Jersey descriptor {@link InstanceBinding}.
     * 

* Along with a new instance, the method is able to register aliases belonging to the new service. * * @param locator HK2 injection manager. * @param dc HK2 Dynamic configuration to bind the object. * @param activeDescriptor HK2 active descriptor. * @param aliases aliases belonging to the given descriptor. */ private static void bindBinding(ServiceLocator locator, DynamicConfiguration dc, ActiveDescriptor activeDescriptor, Set aliases) { ActiveDescriptor boundDescriptor = dc.bind(activeDescriptor); for (AliasBinding alias : aliases) { dc.bind(createAlias(locator, boundDescriptor, alias)); } } static ActiveDescriptor translateToActiveDescriptor(InstanceBinding desc, Type... contracts) { AbstractActiveDescriptor binding; if (contracts.length == 0) { binding = BuilderHelper.createConstantDescriptor(desc.getService()); } else { binding = BuilderHelper.createConstantDescriptor(desc.getService(), null, contracts); } binding.setName(desc.getName()); binding.setClassAnalysisName(desc.getAnalyzer()); if (desc.getScope() != null) { binding.setScope(desc.getScope().getName()); } if (desc.getRank() != null) { binding.setRanking(desc.getRank()); } for (Annotation annotation : desc.getQualifiers()) { binding.addQualifierAnnotation(annotation); } for (Type contract : desc.getContracts()) { binding.addContractType(contract); } if (desc.isProxiable() != null) { binding.setProxiable(desc.isProxiable()); } if (desc.isProxiedForSameScope() != null) { binding.setProxyForSameScope(desc.isProxiedForSameScope()); } return binding; } private static ActiveDescriptor translateToActiveDescriptor(InjectionResolverBinding desc) { ParameterizedTypeImpl parameterizedType = new ParameterizedTypeImpl( org.glassfish.jersey.internal.inject.InjectionResolver.class, desc.getResolver().getAnnotation()); return BuilderHelper.createConstantDescriptor(desc.getResolver(), null, parameterizedType); } /** * Creates the alias object to a provided descriptor. * * @param locator locator used to create an alias. * @param descriptor descriptor which the alias belongs to. * @param alias source of the alias information. * @return populated alias object, ready to bindBinder using {@link DynamicConfiguration}. */ private static org.glassfish.hk2.utilities.AliasDescriptor createAlias(ServiceLocator locator, ActiveDescriptor descriptor, AliasBinding alias) { org.glassfish.hk2.utilities.AliasDescriptor hk2Alias = new org.glassfish.hk2.utilities.AliasDescriptor<>(locator, descriptor, alias.getContract().getName(), null); alias.getQualifiers().forEach(hk2Alias::addQualifierAnnotation); alias.getScope().ifPresent(hk2Alias::setScope); alias.getRank().ifPresent(hk2Alias::setRanking); return hk2Alias; } /** * Creates a new binder and automatically use it to bind the the descriptors in {@code bindConsumer}. * * @param bindConsumer consumer used to process the defined operation with a binder. * @return populated binder. */ private static Binder createBinder(Consumer bindConsumer) { return new AbstractBinder() { @Override protected void configure() { bindConsumer.accept(this); } }; } /** * Transforms Jersey scopes/annotations to HK2 equivalents. * * @param scope Jersey scope/annotation. * @return HK2 equivalent scope/annotation. */ private static Class transformScope(Class scope) { if (scope == PerLookup.class) { return org.glassfish.hk2.api.PerLookup.class; } else if (scope == PerThread.class) { return org.glassfish.hk2.api.PerThread.class; } return scope; } }