[
https://issues.apache.org/jira/browse/FELIX-4853?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=14497077#comment-14497077
]
Pierre De Rop commented on FELIX-4853:
--------------------------------------
Hello Carlos,
Thanks Carlos for trying to improve DM in order to address TCCL management.
However, I tend to think that it's difficult to provide a general solution that
allows to fix every kind of issues related to TCCL.
Some third party libraries requires to set the TCCL on the library classloader
(like in your case), some other requires to set the TCCL to the invoking bundle
(that is invoking the third party library), some other libraries allows to pass
a class loader in the API methods, etc ...
now, regarding your proposal, I think it's also addressing a specific use case:
it assumes that the callback will invoke the injected service. But what if the
callback just stores the injected service, and invokes it later (for example in
the start callback, or from another method) ? And if a client Activator defines
an autoconfig dependency, then the service will be injected on a class field,
and the callback won't be used.
But I think there is another way to address this situation, indeed you could
also define an Aspect service that intercepts all client method invocation in
order to set the TCCL before calling the target service.
For example, let's first define a simple interface for a ThirdPartyService
which requires the TCCL to have visibility on the bundle which registers the
ThirdPartyService (similar to your use case):
{code}
/**
* a Service that internally uses a third party library that needs the TCCL to
be set
* to the ThirdPartyLibrary bundle classloader.
*/
public interface ThirdPartyLibrary {
void invoke();
}
{code}
Now, since you know that the ThirdPartyLibrary implementation needs the TCCL to
be set to the bundle that implements and registers the ThirdPartyLibrary, you
can then define the following aspect service:
{code}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.wiring.BundleWiring;
/**
* Intercept all invocations to the ThirdPartyLibrary service in order to set
the TCCL to the bundle of the ThirdPartyLibrary service.
*/
public class ThirdPartyLibraryAspect implements InvocationHandler {
Object m_service;
ClassLoader m_serviceCL;
protected void bind(ServiceReference ref, Object service) {
m_service = service;
Bundle bundle = ref.getBundle();
BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
m_serviceCL = bundleWiring.getClassLoader();
}
public Object invoke(Object proxy, Method m, Object[] args) throws
Throwable {
Thread currThread = Thread.currentThread();
ClassLoader currThreadCL = currThread.getContextClassLoader();
try {
currThread.setContextClassLoader(m_serviceCL);
return m.invoke(m_service, args);
}
finally {
currThread.setContextClassLoader(currThreadCL);
}
}
}
{code}
Now, let's make the Activator which defines the Aspect service:
{code}
import java.lang.reflect.Proxy;
import org.apache.felix.dm.DependencyActivatorBase;
import org.apache.felix.dm.DependencyManager;
import org.osgi.framework.BundleContext;
/**
* Defines an aspect service that allows to set the TCCL to the bundle that
registers the ThirdPartyLibrary service.
*/
public class Activator extends DependencyActivatorBase {
public void init(BundleContext bc, DependencyManager dm) throws Exception {
dm.add(createAspectService(ThirdPartyLibrary.class, null, 1, "bind",
null, null)
.setFactory(this, "create"));
}
Object create() {
ClassLoader cl = ThirdPartyLibrary.class.getClassLoader();
return Proxy.newProxyInstance(cl, new Class[] { ThirdPartyLibrary.class
},
new ThirdPartyLibraryAspect());
}
}
{code}
So, now, any client that defines a ServiceDependency on the ThirdPartyLibrary
will actually be bound to the aspect service, and each method invocation will
be transparently set to the class loader of the bundle that has registered the
original ThirdPartyLibrary service.
Does this make sense for your usecase ?
/Pierre
> 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)