io.helidon.webserver.jersey.HelidonHK2InjectionManagerFactory Maven / Gradle / Ivy
Show all versions of helidon-webserver-jersey Show documentation
/*
* Copyright (c) 2021, 2023 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.webserver.jersey;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jakarta.annotation.Priority;
import jakarta.inject.Singleton;
import jakarta.ws.rs.core.Application;
import org.glassfish.jersey.inject.hk2.Hk2InjectionManagerFactory;
import org.glassfish.jersey.inject.hk2.ImmediateHk2InjectionManager;
import org.glassfish.jersey.internal.inject.Binder;
import org.glassfish.jersey.internal.inject.Binding;
import org.glassfish.jersey.internal.inject.ForeignDescriptor;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InstanceBinding;
import org.glassfish.jersey.internal.inject.ServiceHolder;
import org.glassfish.jersey.server.ResourceConfig;
/**
* Overrides the injection manager factory from Jersey and provides a new implementation
* of {@code InjectionManager}. This new injection manager will separate registrations
* for those global (shared) providers and those returned by calling {@code getClasses}
* and {@code getSingletons}.
*
* This separation is necessary to properly associate providers with JAX-RS applications,
* of which there could be more than one in Helidon.
*/
@Priority(11) // overrides Jersey's
public class HelidonHK2InjectionManagerFactory extends Hk2InjectionManagerFactory {
private static final Logger LOGGER = Logger.getLogger(HelidonHK2InjectionManagerFactory.class.getName());
@Override
public InjectionManager create(Object parent) {
InjectionManager result;
if (parent == null) {
result = super.create(null);
LOGGER.finest(() -> "Creating injection manager " + result);
} else if (parent instanceof ImmediateHk2InjectionManager) { // single JAX-RS app
result = (InjectionManager) parent;
LOGGER.finest(() -> "Using injection manager for single app case " + result);
} else if (parent instanceof InjectionManagerWrapper) { // multiple JAX-RS apps
InjectionManagerWrapper wrapper = (InjectionManagerWrapper) parent;
InjectionManager forApplication = super.create(null);
result = new HelidonInjectionManager(forApplication, wrapper.injectionManager, wrapper.application);
LOGGER.finest(() -> "Creating injection manager for multi app case " + forApplication
+ " with shared " + wrapper.injectionManager);
} else if (parent instanceof HelidonInjectionManager) {
result = (InjectionManager) parent;
LOGGER.finest(() -> "Re-using existing Helidon injection manager " + result);
} else {
throw new IllegalStateException("Invalid parent injection manager");
}
return result;
}
/**
* Helidon implementation of an injection manager. Based on two underlying injection managers:
* one to handle application specific classes (returned by the {@code Application} subclass
* methods) and one that is shared among all the {@code Application} subclasses. Thus, if a
* Helidon application comprises N subclasses, then N+1 injection managers will be created.
*
* Creating a separate injection manager ensures that providers associated with a certain
* subclass are not returned for others. There will be an instance of this class for each
* {@code Application} subclass. This manager needs to get access to the values returned
* by {@code getClasses} and {@code getInstances} in order to provide the correct registration
* semantics
*/
static class HelidonInjectionManager implements InjectionManager {
private static final Logger LOGGER = Logger.getLogger(HelidonInjectionManager.class.getName());
private final ResourceConfig resourceConfig;
private final InjectionManager shared;
private final InjectionManager forApplication;
HelidonInjectionManager(InjectionManager forApplication, InjectionManager shared, ResourceConfig resourceConfig) {
this.forApplication = forApplication;
this.shared = shared != null ? shared : forApplication; // for testing
this.resourceConfig = resourceConfig;
}
@Override
public void completeRegistration() {
shared.completeRegistration();
forApplication.completeRegistration();
}
@Override
public void shutdown() {
shared.shutdown();
forApplication.shutdown();
}
@Override
public boolean isShutdown() {
return shared.isShutdown() && forApplication.isShutdown();
}
/**
* Registers classes returned by {@code getClasses} in {@code forApplication} and
* all other classes in {@code shared}. This is done to keep separation between
* global providers and those that are specific to an {@code Application} class.
*
* @param binding the binding to register.
*/
@Override
public void register(Binding binding) {
if (returnedByApplication(binding)) {
forApplication.register(binding);
LOGGER.finest(() -> "register forApplication " + forApplication + " " + toString(binding));
} else {
shared.register(binding);
LOGGER.finest(() -> "register shared " + shared + " " + toString(binding));
}
}
@Override
public void register(Iterable descriptors) {
descriptors.forEach(this::register);
}
@Override
public void register(Binder binder) {
binder.getBindings().forEach(this::register);
}
@Override
public void register(Object provider) throws IllegalArgumentException {
if (getSingletons().contains(provider)) {
forApplication.register(provider);
LOGGER.finest(() -> "register forApplication " + forApplication + " " + provider);
} else {
shared.register(provider);
LOGGER.finest(() -> "register shared " + forApplication + " " + provider);
}
}
@Override
public boolean isRegistrable(Class> clazz) {
return shared.isRegistrable(clazz) || forApplication.isRegistrable(clazz);
}
@Override
public T create(Class createMe) {
try {
return shared.create(createMe);
} catch (Throwable t) {
return forApplication.create(createMe);
}
}
@Override
public T createAndInitialize(Class createMe) {
try {
return shared.createAndInitialize(createMe);
} catch (Throwable t) {
return forApplication.createAndInitialize(createMe);
}
}
/**
* Collects all service holders, including those registered in the {@code shared}
* and the {@code forApplication}.
*
* @param contractOrImpl contract or implementation class.
* @param qualifiers the qualifiers.
* @param parameter type.
* @return list of service holders.
*/
@Override
public List> getAllServiceHolders(Class contractOrImpl, Annotation... qualifiers) {
List> sharedList = shared.getAllServiceHolders(contractOrImpl, qualifiers);
sharedList.forEach(sh -> LOGGER.finest(() ->
"getAllServiceHolders shared " + shared + " " + sh.getContractTypes().iterator().next()));
List> forApplicationList = forApplication.getAllServiceHolders(contractOrImpl, qualifiers);
forApplicationList.forEach(sh -> LOGGER.finest(() ->
"getAllServiceHolders forApplication " + forApplication + " " + sh.getContractTypes().iterator().next()));
return forApplicationList.size() == 0 ? sharedList
: Stream.concat(sharedList.stream(), forApplicationList.stream()).collect(Collectors.toList());
}
@Override
public T getInstance(Class contractOrImpl, Annotation... qualifiers) {
T t = shared.getInstance(contractOrImpl, qualifiers);
return t != null ? t : forApplication.getInstance(contractOrImpl, qualifiers);
}
@Override
public T getInstance(Class contractOrImpl, String classAnalyzer) {
T t = shared.getInstance(contractOrImpl, classAnalyzer);
return t != null ? t : forApplication.getInstance(contractOrImpl, classAnalyzer);
}
@Override
public T getInstance(Class contractOrImpl) {
T t = shared.getInstance(contractOrImpl);
return t != null ? t : forApplication.getInstance(contractOrImpl);
}
@Override
public T getInstance(Type contractOrImpl) {
T t = shared.getInstance(contractOrImpl);
return t != null ? t : forApplication.getInstance(contractOrImpl);
}
@Override
public Object getInstance(ForeignDescriptor foreignDescriptor) {
Object o = shared.getInstance(foreignDescriptor);
return o != null ? o : forApplication.getInstance(foreignDescriptor);
}
@Override
public ForeignDescriptor createForeignDescriptor(Binding binding) {
try {
return shared.createForeignDescriptor(binding);
} catch (Throwable t) {
return forApplication.createForeignDescriptor(binding);
}
}
@Override
public List getAllInstances(Type contractOrImpl) {
return Stream.concat(
shared.getAllInstances(contractOrImpl).stream(),
forApplication.getAllInstances(contractOrImpl).stream())
.collect(Collectors.toList());
}
@Override
public void inject(Object injectMe) {
try {
shared.inject(injectMe);
} catch (Throwable t) {
LOGGER.log(Level.WARNING, "Injection failed for " + injectMe + " using shared", t);
forApplication.inject(injectMe);
}
}
@Override
public void inject(Object injectMe, String classAnalyzer) {
try {
shared.inject(injectMe, classAnalyzer);
} catch (Throwable t) {
LOGGER.log(Level.WARNING, "Injection failed for " + injectMe + " using shared", t);
forApplication.inject(injectMe, classAnalyzer);
}
}
@Override
public void preDestroy(Object preDestroyMe) {
shared.preDestroy(preDestroyMe);
forApplication.preDestroy(preDestroyMe);
}
/**
* Calls {@code getClasses} method in resource config.
*
* @return set of classes returned from {@code Application} object.
*/
private Set> getClasses() {
Application application = resourceConfig.getApplication();
return application != null ? resourceConfig.getClasses() : Collections.emptySet();
}
/**
* Calls {@code getSingletons} method in resource config.
*
* @return set of singletons returned from {@code Application} object.
*/
private Set