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;
}
}
There was a bug in the ServiceLocator bridge which has been fixed. The fix will be in hk2 2.5.0-b07 or later!
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;
}
}