[
https://issues.apache.org/jira/browse/FELIX-4853?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=14500688#comment-14500688
]
Pierre De Rop commented on FELIX-4853:
--------------------------------------
Interesing discussion.
So, I'm starting to understand your usecase now. Indeed, as you said you could
use ServiceReference in your Pojo's callbacks, but you don't want your Pojos to
be tied to OSGi api.
And I think that adding a TCCLServiceDependency is a bit too much specific.
Now, let's try to find a a compromise, and see if some design patterns could
help.
I have attached to this issue a bndtools project which contains two samples
that are trying to address your use case.
In the sample code, we have a "Service" interface (ans its corresponding
ServiceImpl). And we have a Pojo object that is injected with the Service.
The first example ("autocloseable") is java7 compatible and delegates the
Pojo's callback in the Activator.
The second example ("tcclcallback") requires java8, and uses java8 method
references in order to easily proxy the Pojo's callbacks using a TCCL proxy
object.
Let's describe the first "autocloseable" example (if you are using java7):
====================================================
first, we have the Service, ServiceImpl, and the Pojo classes:
{code}
public interface Service {
}
public class ServiceImpl implements Service {
}
public class Pojo {
void bind(Service service) {
System.out.println("current TCCL:" +
Thread.currentThread().getContextClassLoader());
}
}
{code}
Now, when using a ServiceDependency, you can provide an external callback (not
the Pojo instance, but a callback that you can implement). So in the following
Activator, we are using a special "TCCL" helper that sets the TCCL to the
bundle classloader of the MyService, and the TCCL is then restaured in the
close method:
{code}
/**
* Helper used to set a bundle TCCL and restore the previous TCCL on close
method.
*/
public class TCCL implements AutoCloseable {
private final ClassLoader current =
Thread.currentThread().getContextClassLoader();
public TCCL(ServiceReference<?> ref) {
Bundle bundle = ref.getBundle();
BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
Thread.currentThread().setContextClassLoader(bundleWiring.getClassLoader());
}
@Override
public void close() {
Thread.currentThread().setContextClassLoader(current);
}
}
{code}
Now, the Activator just delegate the Pojo callback to the Activator.bind method
wich uses the TCCL helper in order to manage the TCCL. The Activator.bind
method takes as parameter the Pojo's Component object which can be used to get
the actual Pojo instance (with Component.getInstance() method):
{code}
/**
* This Activator handles all MyClient callbacks in order to set the TCCL
before actually injected the service.
*/
public class Activator extends DependencyActivatorBase {
@Override
public void init(BundleContext bc, DependencyManager dm) throws Exception {
dm.add(createComponent()
.setImplementation(ServiceImpl.class)
.setInterface(Service.class.getName(), null));
dm.add(createComponent()
.setImplementation(Pojo.class)
.add(createServiceDependency().setService(Service.class).setCallbacks(this,
"bind", null)));
}
void bind(Component component, ServiceReference<?> ref, Service service) {
try (TCCL tccl = new TCCL(ref)) {
((Pojo) component.getInstance()).bind(service);
}
}
}
{code}
Now let's describe the second "tcclcallback" example (if you are using java8):
=========================================================
If you are using java8, we can reduce the "code ceremony" by using Method
References and a BiConsumer interface.
Here is the Activator:
{code}
/**
* Activator which proxies the Pojo's callback using a TCCLCallback that does
the TCCL management.
*/
public class Activator extends DependencyActivatorBase {
@Override
public void init(BundleContext bc, DependencyManager dm) throws Exception {
dm.add(createComponent()
.setImplementation(ServiceImpl.class)
.setInterface(Service.class.getName(), null));
TCCLCallback<Pojo, Service> callback = new TCCLCallback<>(Pojo::bind);
dm.add(createComponent()
.setImplementation(Pojo.class)
.add(createServiceDependency().setService(Service.class).setCallbacks(callback,
"bind", null)));
}
}
{code}
In the code above, the "callback" object is passed to the service dependency.
So, the Service will be injected first in the
TCCLCallback instance. and that object will set the TCCL to the Service's
bundle's classloader, then it will inject the Service in the Pojo::bind method,
and it will then restore the initial TCCL.
Here is the code for the TCCLCallback helper:
{code}
/**
* Tool that allows to delegate a dependency callback with TCCL set to the
injected service dependency.
*
* @param <T> The type of the class where the service dependency has to be
injected to, but with TCCL set to the injected service class loader.
* @param <U> The type of the injected service.
*/
public class TCCLCallback<T, U> {
private final BiConsumer<T, U> consumer;
public TCCLCallback(BiConsumer<T, U> consumer) {
this.consumer = consumer;
}
void bind(Component component, ServiceReference ref, U service) {
Thread t = Thread.currentThread();
ClassLoader current = t.getContextClassLoader();
Bundle bundle = ref.getBundle();
BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
ClassLoader cl = bundleWiring.getClassLoader();
try {
t.setContextClassLoader(cl);
T instance = component.getInstance();
consumer.accept(instance, service);
} finally {
t.setContextClassLoader(current);
}
}
}
{code}
would this help ?
> Create a new ServiceDependency that sets the TCCL to the incoming
> servicereference bundle's classloader before invoking callbaks
> --------------------------------------------------------------------------------------------------------------------------------
>
> Key: FELIX-4853
> URL: https://issues.apache.org/jira/browse/FELIX-4853
> Project: Felix
> Issue Type: New Feature
> Components: Dependency Manager
> Affects Versions: dependencymanager-3.2.0
> Reporter: Carlos Sierra
>
> The invoke method would go like this:
> {code:title=TCCLServiceDependencyImpl.java|borderStyle=solid}
> @Override
> @SuppressWarnings("rawtypes")
> public void invoke(
> Object[] callbackInstances, DependencyService dependencyService,
> ServiceReference reference, Object service, String name) {
> Bundle bundle = reference.getBundle();
> BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
> ClassLoader bundleClassLoader = bundleWiring.getClassLoader();
> Thread currentThread = Thread.currentThread();
> ClassLoader contextClassLoader =
> currentThread.getContextClassLoader();
> currentThread.setContextClassLoader(bundleClassLoader);
> try {
> super.invoke(
> callbackInstances, dependencyService,
> reference, service, name);
> }
> finally {
> currentThread.setContextClassLoader(contextClassLoader);
> }
> }
> {code}
> If you think this is useful I can provide a patch. Which version and which
> repo should I use for it?
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)