How can I validate a Guice scope's usage in tests? - java

I have some tests that I would like to have fail if certain Guice scopes are used incorrectly. For example, a #Singleton should not have any #RequestScoped or #TestScoped dependencies (Provider<>s are okay, of course).
In production, this is partially solved because eagerly-bound singletons will be constructed before the scope is entered, resulting in OutOfScopeExceptions. But in development, the singleton will be created lazily while inside the scope, and no problems are evident.
Judging by these two open issues, it seems like there is no easy, built-in way to do this. Can I achieve this using the SPI? I tried using a TypeListener but it's not clear how to get the dependencies of a given type.

this is not a trivial problem, but definitely it is a good question! There could be a tester for scope binding problems you mentioned. I think i could make a Junit runner to generate warning with wrong binding practice. I will update this post later with it.
For now there is a example how to get binding scopes.
Module
public class ScopeTestModel extends ServletModule {
#Override
protected void configureServlets() {
super
.configureServlets();
bind(Key.get(Object.class, Names.named("REQ1"))).to(Object.class).in(ServletScopes.REQUEST);
bind(Key.get(Object.class, Names.named("REQ2"))).to(RequestScopedObject.class);
bind(Key.get(Object.class, Names.named("SINGLETON1"))).to(Object.class).asEagerSingleton();
bind(Key.get(Object.class, Names.named("SINGLETON2"))).to(Object.class).in(Scopes.SINGLETON);
bind(Key.get(Object.class, Names.named("SINGLETON3"))).to(SingletonScopedObject.class);
bind(Key.get(Object.class, Names.named("SESS1"))).to(Object.class).in(ServletScopes.SESSION);
bind(Key.get(Object.class, Names.named("SESS2"))).to(SessionScopedObject.class);
}
}
TestCase
public class TestScopeBinding {
private Injector injector = Guice.createInjector(new ScopeTestModel());
#Test
public void testRequestScope() throws Exception {
Binding<Object> req1 = injector.getBinding(Key.get(Object.class, Names.named("REQ1")));
Binding<Object> req2 = injector.getBinding(Key.get(Object.class, Names.named("REQ2")));
Scope scope1 = getScopeInstanceOrNull(req1);
Scope scope2 = getScopeInstanceOrNull(req2);
Assert.assertEquals(ServletScopes.REQUEST,scope1);
Assert.assertEquals(ServletScopes.REQUEST,scope2);
}
#Test
public void testSessionScope() throws Exception {
injector.getAllBindings();
Binding<Object> sess1 = injector.getBinding(Key.get(Object.class, Names.named("SESS1")));
Binding<Object> sess2 = injector.getBinding(Key.get(Object.class, Names.named("SESS2")));
Scope scope1 = getScopeInstanceOrNull(sess1);
Scope scope2 = getScopeInstanceOrNull(sess2);
Assert.assertEquals(ServletScopes.SESSION,scope1);
Assert.assertEquals(ServletScopes.SESSION,scope2);
}
#Test
public void testSingletonScope() throws Exception {
injector.getAllBindings();
Binding<Object> sng1 = injector.getBinding(Key.get(Object.class, Names.named("SINGLETON1")));
Binding<Object> sng2 = injector.getBinding(Key.get(Object.class, Names.named("SINGLETON2")));
Binding<Object> sng3 = injector.getBinding(Key.get(Object.class, Names.named("SINGLETON3")));
Scope scope1 = getScopeInstanceOrNull(sng1);
Scope scope2 = getScopeInstanceOrNull(sng2);
Scope scope3 = getScopeInstanceOrNull(sng3);
Assert.assertEquals(Scopes.SINGLETON,scope1);
Assert.assertEquals(Scopes.SINGLETON,scope2);
Assert.assertEquals(Scopes.SINGLETON,scope3);
}
private Scope getScopeInstanceOrNull(final Binding<?> binding) {
return binding.acceptScopingVisitor(new DefaultBindingScopingVisitor<Scope>() {
#Override
public Scope visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
throw new RuntimeException(String.format("I don't know how to handle the scopeAnnotation: %s",scopeAnnotation.getCanonicalName()));
}
#Override
public Scope visitNoScoping() {
if(binding instanceof LinkedKeyBinding) {
Binding<?> childBinding = injector.getBinding(((LinkedKeyBinding)binding).getLinkedKey());
return getScopeInstanceOrNull(childBinding);
}
return null;
}
#Override
public Scope visitEagerSingleton() {
return Scopes.SINGLETON;
}
public Scope visitScope(Scope scope) {
return scope;
}
});
}
}
Scoped objects
#RequestScoped
public class RequestScopedObject extends Object {
}
#SessionScoped
public class SessionScopedObject extends Object {
}
#Singleton
public class SingletonScopedObject extends Object {
}

Here's how I've accomplished this with the 4.0 beta of Guice, using ProvisionListener. I tried TypeListener but it seems that TypeListeners get called before Guice necessarily has bindings for that type's dependencies. This caused exceptions, and even a deadlock in one case.
private static class ScopeValidator implements ProvisionListener {
private #Inject Injector injector;
private #Inject WhateverScope scope;
#Override
public <T> void onProvision(ProvisionInvocation<T> provision) {
if (injector == null) {
// The injector isn't created yet, just return. This isn't a
// problem because any scope violations will be caught by
// WhateverScope itself here (throwing an OutOfScopeException)
return;
}
Binding<?> binding = provision.getBinding();
Key<?> key = binding.getKey();
if (Scopes.isSingleton(binding) && binding instanceof HasDependencies) {
Set<Dependency<?>> dependencies = ((HasDependencies) binding).getDependencies();
for (Dependency<?> dependency : dependencies) {
Key<?> dependencyKey = dependency.getKey();
Binding<?> dependencyBinding = injector.getExistingBinding(dependencyKey);
if (dependencyBinding != null && Scopes.isScoped(dependencyBinding, whateverScope, WhateverScoped.class)) {
throw new ProvisionException(String.format(
"Singleton %s depends on #WhateverScoped %s",
key, dependencyKey));
}
}
}
}
}

Related

Wire collection of objects dynamically in Guice

Guice newbie here, with a complicated scenario.
My company has a large number of constants of a given type (let's call them Thingy) that belong to different teams and are maintained in different parts of our application. However, we need to have a central registry that knows about all of them (let's call this the ThingyService). I am writing a base module that teams can either extend or install, with the purpose of allowing a team to register their Thingys, and giving them access to the ThingyService. This module takes as parameter a list of classes from which I can extract the Thingy constants, this part is working fine.
What I don't understand is how I can a) make each module know about each other module's list of Thingys and b) how I can create my ThingyService as a singleton that contains all of my Thingys. I have experimented with shared static state and with ThreadLocals, but I keep either breaking tests or breaking my main (play) application. In my naive understanding of Guice, I think I need a MultiBinder for the Thingys, but I don't see how I can share that between modules. Here's what I'd like to do:
class ThingyModule extends AbstractModule{
final Set<Class<?>> myThingyClasses; // this is populated in the constructor
private Set<Thingy> extractThingiesFromThingyClasses(){
// I have this working
}
#Provides #Singleton ThingyService thingyService(
Set<Thingy> thingys // all thingys, from all such modules
){
return new ThingyService(thingys);
}
protected void configure(){
extractThingiesFromThingyClasses().forEach(thingy->
// bind thingy to a global MultiBinder?
);
}
}
How can I make my ThingyService unique and global, with all the Thingys from the entire application? Note: I don't necessarily need my Thingys to be managed by Guice, the only place I need them is in ThingyService. Also, this is a play / scala application if that makes a difference, but my ThingyModule code lives in a library written in Java.
It turns out I omitted one important detail, Thingy has a type parameter, it's actually Thingy<T>, and that's the reason it didn't work before. By cheating and registering Thingy as raw type, and then also injecting it as raw type, I got it to work.
Here is a complete working example using JUnit 5 and AssertJ:
class ThingyModuleTest {
static class Thingy<T>{
private final T value;
Thingy(final T value) {this.value = value;}
#Override public boolean equals(final Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
final Thingy<?> thingy = (Thingy<?>) o; return Objects.equals(value, thingy.value); }
#Override public int hashCode() { return Objects.hash(value); }
}
#Singleton
static class ThingyService{
final Set<Thingy<?>> thingies;
#SuppressWarnings({"unchecked", "rawtypes"}) #Inject
ThingyService(Set<Thingy> thingies) {
this.thingies = ImmutableSet.copyOf((Set)thingies);
}
public Set<Thingy<?>> getThingies() { return thingies; }
}
abstract static class ThingyModule extends AbstractModule {
private final Set<Class<?>> classesToScan;
public ThingyModule(Class<?>... classes) {
this.classesToScan = ImmutableSet.copyOf(classes);
}
private Set<Thingy<?>> scanForThingies(){
return classesToScan.stream()
.flatMap(c-> Arrays.stream(c.getDeclaredFields()))
.filter(f->f.getType().isAssignableFrom(Thingy.class))
.filter(f-> Modifier.isStatic(f.getModifiers())&&Modifier.isFinal(f.getModifiers()))
.map(this::readThingy)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toSet());
}
#SuppressWarnings("unchecked")
private Optional<Thingy<?>> readThingy(final Field field) {
try{
field.setAccessible(true);
return Optional.ofNullable(field.get(null))
.filter(Thingy.class::isInstance)
.map(Thingy.class::cast);
} catch (IllegalAccessException e) { return Optional.empty(); }
}
#Override protected void configure() {
bind(ThingyService.class);
#SuppressWarnings("rawtypes") Multibinder<Thingy> multibinder = Multibinder.newSetBinder(binder(), Thingy.class);
scanForThingies().forEach(thingy -> multibinder.addBinding().toInstance(thingy));
}
}
static class ThingyModule1 extends ThingyModule {
public ThingyModule1() { super(Thingies1.class); }
static class Thingies1{
static final Thingy<Boolean> BooleanThingy = new Thingy<>(true);
static final Thingy<Integer> IntThingy = new Thingy<>(123);
}
}
static class ThingyModule2 extends ThingyModule {
public ThingyModule2() { super(Thingies2.class); }
static class Thingies2{
static final Thingy<String> StringThingy = new Thingy<>("hello");
static final Thingy<Long> LongThingy = new Thingy<>(123L);
}
}
#Test void validateThingyService() {
ThingyService thingyService = Guice.createInjector(new ThingyModule1(), new ThingyModule2())
.getProvider(ThingyService.class)
.get();
assertThat(thingyService).isNotNull()
.extracting(ts -> ImmutableList.copyOf(ts.getThingies()))
.asList()
.containsExactlyInAnyOrder(BooleanThingy, IntThingy, StringThingy, LongThingy);
}
}
I will mark this answer as accepted until somebody else provides a more idiomatic one.

Register BeanFactory in SpringBoot

First I'm not sure if it's a good idea to do all this.
Goal is to create some interfaces with annotations to hide legacy position based string access out of a configuration database, without implementing each interface.
Declarative configured Interface:
public interface LegacyConfigItem extends ConfigDbAccess{
#Subfield(length=3)
String BWHG();
#Subfield(start = 3, length=1)
int BNKST();
#Subfield(start = 4, length=1)
int BEINH();
:
}
Base interface for runtime identification
public interface ConfigDbAccess{
}
Dummy implementation without functionality, may change.
public class EmptyImpl {
}
Beanfactory and MethodInvocation interceptor, to handle the unimplemented methods.
#Component
public class InterfaceBeanFactory extends DefaultListableBeanFactory {
protected static final int TEXT_MAX = 400;
#Autowired
private EntityRepo entityRepo;
public <T> T getInstance(Class<T> legacyInterface, String key) {
ProxyFactory factory = new ProxyFactory(new EmptyImpl());
factory.setInterfaces(legacyInterface);
factory.setExposeProxy(true);
factory.addAdvice(new MethodInterceptor() {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
KEY keyAnnotation = invocation.getThis().getClass().getAnnotation(Key.class);
String key= keyAnnotation.key().toUpperCase();
String ptart = invocation.getMethod().getDeclaringClass().getSimpleName();
Vpt result = entityRepo.getOne(new EntityId(ptart.toUpperCase(), schl.toUpperCase()));
Subfield sub = invocation.getMethod().getAnnotation(Subfield.class);
//TODO: Raise missing Subfield annotation
int start = sub.start();
int length = sub.length();
if (start + length > TEXT_MAX) {
//TODO: Raise invalid Subfield config
}
String value = result.getTextField().substring(start,start+length);
return value;
}
});
return (T) factory.getProxy();
}
#Override
protected Map<String, Object> findAutowireCandidates(String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
Map<String, Object> map = super.findAutowireCandidates(beanName, requiredType, descriptor);
if (ConfigDbAccess.class.isAssignableFrom(requiredType )) {
:
#SpringBootApplication
public class JpaDemoApplication {
#Autowired
private ApplicationContext context;
public static void main(String[] args) {
SpringApplication app = new SpringApplication(JpaDemoApplication.class);
// app.setApplicationContextClass(InterfaceInjectionContext .class);
app.run(args);
}
public class InterfaceInjectionContext extends AnnotationConfigApplicationContext {
public VptInjectionContext () {
super (new InterfaceBeanFactory ());
}
}
So far I got all this stuff working, except when I try to set the applications Context class to my DefaultListableBeanFactory, I'm killing the Spring boot starter web. The application starts, injects the the Autowired fields with my intercepted pseudo implementaition --- and ends.
I think I'm doing something wrong with registering the DefaultListableBeanFactory, but I've no idea how to do it right.
To get this answered:
M. Deinum pointed me to a much simpler solution:
Instead of creating a BeanFactory I installed a BeanPostProcessor with this functioniality.
#RestController
public class DemoRestController {
#Autowired
VptService vptService;
#ConfigItem(key="KS001")
private PrgmParm prgmKs001;
#ConfigItem(key="KS002")
private PrgmParm prgmKs002;
public DemoRestController() {
super();
}
Where the ConfigItem annotation defines the injection point.
Next I created a CustomBeanPostProcessor which scans all incoming beans for
fields having a ConfigItem annotation
#Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
for (Field field : bean.getClass().getDeclaredFields()) {
SHL cfgDef = field.getAnnotation(ConfigItem.class);
if (cfgDef != null) {
Object instance = getlInstance(field.getType(), cfgDef.key());
boolean accessible = field.isAccessible();
field.setAccessible(true);
try {
field.set(bean, instance);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
field.setAccessible(accessible);
}
}
return bean;
}
The getInstnce(field.getType(),cfgDef.key()) creates a proxy with the MethodInterceptor, which does the work.
There are a lot of things to finalize, but all in all it looks good to me.

Tapestry: Inject at runtime

again a small problem by understanding "how tapestry works".
I've got a Tapestry component (in this case a value encoder):
public class EditionEncoder implements ValueEncoder<Edition>, ValueEncoderFactory<Edition> {
#Inject
private IEditionManager editionDao;
public EditionEncoder(IEditionManager editionDao) {
this.editionManager = editionDao;
}
#Override
public String toClient(Edition value) {
if(value == null) {
return "";
}
return value.getName();
}
#Override
public Edition toValue(String clientValue) {
if(clientValue.equals("")) {
return null;
}
return editionManager.getEditionByName(clientValue);
}
#Override
public ValueEncoder<Edition> create(Class<Edition> type) {
return this;
}
}
Injecting the the Manager is not working, because the Encoder is created within a page like that:
public void create() {
editionEncoder = new EditionEncoder();
}
casued by this, i'm forced to use this ugly solution:
#Inject
private IEditionManager editionmanager;
editionEncoder = new EditionEncoder(editionManager);
Is there a better way to inject components during runtime or is there a better solution in general for it?
Thanks for your help in advance,
As soon as you use "new" then tapestry-ioc is not involved in object creation and can't inject. You should inject everything and never use "new" for singleton services. This is true for all ioc containers, not just tapestry-ioc.
Also if you put #Inject on a field then you don't also need a constructor to set it. Do one or the other, never both.
You should do something like this:
public class MyAppModule {
public void bind(ServiceBinder binder) {
binder.bind(EditionEncoder.class);
}
}
Then in your page/component/service
#Inject EditionEncoder editionEncoder;
If you wanted to put your own instantiated objects in there you can do
public class MyServiceModule {
public void bind(ServiceBinder binder) {
binder.bind(Service1.class, Service1Impl.class);
binder.bind(Service2.class, Service2Impl.class);
}
public SomeService buildSomeService(Service1 service1, Service2 service2, #AutoBuild Service3Impl service3) {
Date someDate = new Date();
return new SomeServiceImpl(service1, service2, service3, someDate);
}
}

Cucumber with Guice - multiple guice injector

I'm using Cucumber with Guice as DI.
I've encountered following problem:
I've got one step i.e.
class MyStep() {
#Inject
private MyService myService;
#Given("Some acction happen")
public void sthHappen() {
myService.doSth();
}
}
And I've got this class to run it as JUnit test
#RunWith(Cucumber.class)
#CucumberOptions(...)
public class MyTest {
}
There is a
class MyModule extends AbstractModule {
#Override
protected void configure() {
bind(MyService.class).to(MyFirstService.class);
}
}
which is used by my MyInjectorSource
I define cucumber.properties where I define guice.injector-source=MyInjectorSource;
There is also a feature file with scenario.
Everything is working for now.
And no i would like to run MyStep step with other MyService implementation (of course I don't wont to duplicate code of MyStep)
I define a new feature file with new scenarios, and new Test class
#RunWith(Cucumber.class)
#CucumberOptions(...)
public class MyOtherTest {
}
And now I've tried to create another InjectorSource but I was not able to configure it.
Solution which I've found is using custom Junit4 runner inheriting from original Cucumber runner and changing its createRuntime method.
Latest cucumber-guice 1.2.5 uses few stages to create injector and unfortunately it uses global variable cucumber.runtime.Env.INSTANCE. This variable is populated from cucumber.properties and System.getProperties.
Flow is:
Cucumber runner scans available backends (in my setup it is cucumber.runtime.java.JavaBackend)
One of JavaBackend constructor loads available ObjectFactory (in my setup it is cucumber.runtime.java.guice.impl.GuiceFactory)
GuiceFactory via InjectorSourceFactory checks Env.INSTANCE, it will create custom InjectorSource or default injector
Ideally cucumber should pass its 'RuntimeOptions` created at start to backend and InjectorSource but unfortunately it doesn't and uses global variable. It is not easy create patch like this one so my solution simplifies this approach and directly create InjectorSource in custom runner by reading new annotation.
public class GuiceCucumberRunner extends Cucumber {
public GuiceCucumberRunner(Class<?> clazz) throws InitializationError, IOException {
super(clazz);
}
#Override
protected Runtime createRuntime(ResourceLoader resourceLoader, ClassLoader classLoader, RuntimeOptions runtimeOptions) throws InitializationError, IOException {
Runtime result = new Runtime(resourceLoader, classLoader, Arrays.asList(createGuiceBackend()), runtimeOptions);
return result;
}
private JavaBackend createGuiceBackend() {
GuiceCucumberOptions guiceCucumberOptions = getGuiceCucumberOptions();
InjectorSource injectorSource = createInjectorSource(guiceCucumberOptions.injectorSource());
ObjectFactory objectFactory = new GuiceFactory(injectorSource.getInjector());
JavaBackend result = new JavaBackend(objectFactory);
return result;
}
private GuiceCucumberOptions getGuiceCucumberOptions() {
GuiceCucumberOptions guiceCucumberOptions = getTestClass().getJavaClass().getAnnotation(GuiceCucumberOptions.class);
if (guiceCucumberOptions == null) {
String message = format("Suite class ''{0}'' is missing annotation GuiceCucumberOptions", getTestClass().getJavaClass());
throw new CucumberException(message);
}
return guiceCucumberOptions;
}
private InjectorSource createInjectorSource(Class<? extends InjectorSource> clazz) {
try {
return clazz.newInstance();
} catch (Exception e) {
String message = format("Instantiation of ''{0}'' failed. InjectorSource must have has a public zero args constructor.", clazz);
throw new InjectorSourceInstantiationFailed(message, e);
}
}
static class GuiceFactory implements ObjectFactory {
private final Injector injector;
GuiceFactory(Injector injector) {
this.injector = injector;
}
#Override
public boolean addClass(Class<?> clazz) {
return true;
}
#Override
public void start() {
injector.getInstance(ScenarioScope.class).enterScope();
}
#Override
public void stop() {
injector.getInstance(ScenarioScope.class).exitScope();
}
#Override
public <T> T getInstance(Class<T> clazz) {
return injector.getInstance(clazz);
}
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE })
public #interface GuiceCucumberOptions {
Class<? extends InjectorSource> injectorSource();
}
#RunWith(GuiceCucumberRunner.class)
#GuiceCucumberOptions(injectorSource = MyInjector.class)
#CucumberOptions(
...
)
public class Suite {
}
I needed to copy GuiceFactory because it doesn't exposes normal constructor (!)

How do I make a HK2 ServiceLocator use the Singleton service instances from the ServiceLocator that it's bridged from?

We are using using ExtrasUtilities.bridgeServiceLocator() to inject existing Singleton application services created in one ServiceLocator into Jersey RESTful Web Services by bridging the app ServiceLocator into the Jersey ServiceLocator.
However the Singletons that exist in the 'outer' locator aren't being used - each of the services is being created once again when injected into the Jersey services. It seems the Singleton is only visible within the scope of a ServiceLocator, even if it's bridged.
Is this the intended behaviour? And if so is there any way to change this behaviour and have a true Singleton across bridged ServiceLocators?
I have extracted the issue out into a set of test classes that illustrate the point below:
public class BridgedServiceTest
{
private ServiceLocator _outerServiceLocator;
private ServiceLocator _innerServiceLocator;
#Test
public void testBridgedInnerServiceOK() throws Exception
{
ServiceLocatorFactory serviceLocatorFactory = ServiceLocatorFactory.getInstance();
_outerServiceLocator = serviceLocatorFactory.create("Outer");
ServiceLocatorUtilities.addClasses(_outerServiceLocator, SingletonServiceImpl.class);
_innerServiceLocator = serviceLocatorFactory.create("Inner");
ExtrasUtilities.bridgeServiceLocator(_innerServiceLocator, _outerServiceLocator);
final Client client1 = new Client();
_outerServiceLocator.inject(client1);
assertThat(SingletonServiceImpl.instanceCount.get(), Matchers.is(1));
client1.test();
final Client client2 = new Client();
_innerServiceLocator.inject(client2);
// next line fails as instance count is 2
assertThat(SingletonServiceImpl.instanceCount.get(), Matchers.is(1));
client2.test();
}
#After
public void tearDown() throws Exception
{
_innerServiceLocator.shutdown();
_outerServiceLocator.shutdown();
}
}
#Contract
public interface SingletonService
{
void fulfil();
}
#Service
public class SingletonServiceImpl implements SingletonService
{
public static AtomicInteger instanceCount = new AtomicInteger();
#PostConstruct
public void postConstruct()
{
instanceCount.incrementAndGet();
}
#Override
public void fulfil()
{
System.out.println("Contract Fulfilled.");
}
}
public class Client
{
#Inject
private SingletonService _singletonService;
public void test()
{
_singletonService.fulfil();
}
public SingletonService getSingletonService()
{
return _singletonService;
}
}
In the meantime (and in case it is indeed designed behaviour) I have achieved a workaround by implementing my own InjectionResolver. It's not a complete solution as it only works for injection (i.e. a call to ServiceLocator.getService() will not bridge) but it does what I need for now.
Here is the class if anyone needs it. Instead of calling ExtrasUtilities.bridgeServiceLocator(_innerServiceLocator, _outerServiceLocator); you call BridgingInjectionResolver.bridgeInjection(_innerServiceLocator, _outerServiceLocator);
#Singleton
#Rank(1)
public class BridgingInjectionResolver implements InjectionResolver<Inject>
{
#Inject
private ServiceLocator _localServiceLocator;
private ServiceLocator _remoteServiceLocator;
public BridgingInjectionResolver()
{
}
/**
* This method will bridge injection of all non-local services from the from ServiceLocator into the into
* ServiceLocator. The two ServiceLocators involved must not have a parent/child relationship
*
* #param into The non-null ServiceLocator that will be able to inject services from the from ServiceLocator
* #param from The non-null ServiceLocator that will provide services for injection to the into ServiceLocator
*/
public static void bridgeInjection(ServiceLocator into, ServiceLocator from)
{
checkParentage(into, from);
checkParentage(from, into);
ServiceLocatorUtilities.addClasses(into, BridgingInjectionResolver.class);
into.getService(BridgingInjectionResolver.class).setRemoteServiceLocator(from);
}
private static void checkParentage(ServiceLocator a, ServiceLocator b)
{
ServiceLocator originalA = a;
while (a != null) {
if (a.getLocatorId() == b.getLocatorId()) {
throw new IllegalStateException("Locator " + originalA + " is a child of or is the same as locator " + b);
}
a = a.getParent();
}
}
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> root)
{
ActiveDescriptor<?> ad = _localServiceLocator.getInjecteeDescriptor(injectee);
if (ad != null) {
return _localServiceLocator.getService(ad, root, injectee);
}
ad = _remoteServiceLocator.getInjecteeDescriptor(injectee);
if ((ad != null) && (ad.getDescriptorVisibility() == DescriptorVisibility.LOCAL)) {
ad = null;
}
if (ad == null) {
if (injectee.isOptional()) {
return null;
}
throw new MultiException(new UnsatisfiedDependencyException(injectee));
}
return _remoteServiceLocator.getService(ad, root, injectee);
}
#Override
public boolean isConstructorParameterIndicator()
{
return false;
}
#Override
public boolean isMethodParameterIndicator()
{
return false;
}
private void setRemoteServiceLocator(ServiceLocator remoteServiceLocator)
{
_remoteServiceLocator = remoteServiceLocator;
}
}
There was a bug in the ServiceLocator bridge which has been fixed. The fix will be in hk2 2.5.0-b07 or later!

Categories