/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
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<Binding> 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<AbstractBinder> bindConsumer = binder -> {
Supplier<?> supplier = binding.getSupplier();
boolean disposable = DisposableSupplier.class.isAssignableFrom(supplier.getClass());
// Bind the Supplier itself to be able to inject - Supplier<T> or DisposableSupplier<T>;
// 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<? extends Supplier<?>> 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.
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<AbstractBinder> bindConsumer = binder -> {
boolean disposable = DisposableSupplier.class.isAssignableFrom(binding.getSupplierClass());
// Bind the Supplier itself to be able to inject - Supplier<T> 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.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, 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}.
* <p>
* 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<AliasBinding> aliases) {
ActiveDescriptor<Object> 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(), 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<AbstractBinder> 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<? extends Annotation> transformScope(Class<? extends Annotation> 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;
}
}