Hi Scott, Hi Carsten,

Overridding ObjectInputStream::resolveClass was what I needed.  My
"fetch" method has 3 variants:

/* uses Thread::getContextClassLoader */
Object fetch(String id)

/* uses type.getClass().getClassLoader(), may not work if "type" is an
interface or superclass, mainly useful for a bundle's internal types
only */
<T> T fetchAs(String id, Class<T> type)

/* more flexible, caller must understand how things work, eg: type can
be an interface type, the definer can be a lambda capable of providing
a reference to an implementation bundle's classloader */
<T> T fetchAs(String id, Class<T> type, Supplier<ClassLoader> definer)

The last signature variant makes it possible to get similar behavior
to the IClassResolver example suggested by Scott (the filter approach
is clever, but might have a higher maintenance cost ; the use of a
Supplier function makes it possible to delegate to such an
implementation however).  I considered the PackageAdmin (bundle
wiring) suggestion by Carsten, but I figured it might deserialise the
wrong class version if multiple bundles define similar classes (the
first match might be incorrect, it seems non-trivial to correctly and
efficiently select a classloader without a "brute force, trial and
error" approach).

I had also considered providing my service using a ServiceFactory,
which could access the consumer's bundle classloader automatically,
however if the code calling "store" is in a different bundle from that
calling "fetch", it wouldn't help much.  Perhaps it would be possible
to combine this approach with use of WeakReferences (referring to the
classloader of the bundle that invoked "store"), automatically
invaliding the stored (serialized) object if the "storer" classloader
was de-referenced... but I felt that that approach was _too_
aggressive (because all I can assume was that the "storer" classloader
was able to find the classloader -- via imported packages -- and not
assume that it was the actual classloader).

For my use cases, that seems like the best trade-off for deterministic
behavior, small API surface, and flexibility (all the other "smart"
cases would seem to be vulnerable to side effects).

Thanks for your suggestions, I hope this message (once archived) is of
use to others.

--
Christopher



On 26 October 2016 at 20:21, Scott Lewis <[email protected]> wrote:
> Another (somewhat similar to Carsten's) approach to this is the one that
> we've implemented in ECF's Remote Services/RSA implementation [1].
>
> We've got a service interface:  IClassResolver [2] and a ObjectInputStream
> subclass called ClassResolverObjectInputStream. The way that this works is
> that when creating a ClassResolverInputStream one specifies a filter for
> finding (via service registry) the IClassResolver service instance to use to
> resolve the class upon first deserialization.   For example, a particular
> bundle (with version) that is to be responsible for resolving the classes
> (bundle.loadClass) for that ObjectInputStream [4].   Of course, other/custom
> implementations of IClassResolver are possible as well, but so far we have
> found the BundleClassResolver + ClassResolverObjectInputStream useful.
>
> Scott
>
> [1] http://wiki.eclipse.org/Eclipse_Communication_Framework_Project
> [2]
> http://download.eclipse.org/rt/ecf/latest/javadoc/org/eclipse/ecf/core/util/IClassResolver.html
> [3]
> http://download.eclipse.org/rt/ecf/latest/javadoc/org/eclipse/ecf/core/util/ClassResolverObjectInputStream.html
> [4]
> http://download.eclipse.org/rt/ecf/latest/javadoc/org/eclipse/ecf/core/util/BundleClassResolver.html
>
>
> On 10/26/2016 9:03 AM, Carsten Ziegeler wrote:
>>
>> Christopher Brown wrote
>>>
>>> Hello,
>>>
>>> I need to define an API for an OSGi service capable of persisting
>>> arbitrary
>>> Serializable objects as byte arrays (to disk, or over the network), and
>>> then capable of deserializing the object via the API.  The objects to be
>>> persisted will almost always be defined by another bundle, so the bundle
>>> actually performing the serialization is almost always going to be unable
>>> to access the classloader that originally provided the definition of the
>>> serialized class (and any non-primitive attributes of that class).
>>>
>>> Any ideas upon how to use java.io.ObjectInputStream such that its
>>> .readObject() method can be forced to use an appropriate classloader?  I
>>> can't see how to override its default behavior.
>>>
>>> As for the API I need to implement (my service), it would be along the
>>> lines of:
>>>
>>> void service.store(String id, Object value);
>>>
>>> <T> T service.fetch(String id, Class<T> implementationType)
>>>
>>> ...where implementationType would need to be equivalent to
>>> value.getClass()
>>> (and not a superclass or implemented interface).  The "fetch" method
>>> would
>>> need the "implementationType" parameter to access the CURRENT version of
>>> the classloader (I can't store the classloader in the "store" method,
>>> first
>>> off because I can't serialize arbitrary classloaders -- via
>>> value.getClass().getClassloader() -- and also because the classloader
>>> might
>>> be the wrong version, if "value" was defined by a bundle that has since
>>> been reloaded).
>>>
>>> Even if I replaced the "implementationType" parameter with a classloader
>>> reference (assuming the caller of the code knew which classloader to
>>> use),
>>> I still don't know how to override ObjectInputStream's default
>>> classloading
>>> behavior.
>>>
>>> Any ideas ?
>>>
>> You can create a subclass of ObjectInputStream and overwrite the
>> resolveClass method to solve the first project.
>>
>> Some time ago we wrote some code, that was using a dynamic class loader
>> in such a subclass that simply used Package Admin to find the bundle
>> providing the class and then using the corresponding class loader. Of
>> course this requires that the classes you want to load are publicly
>> exported.
>>
>> I'm not saying this is the best solution, but it did the trick for us.
>>
>> Regards
>>
>>   Carsten
>>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [email protected]
> For additional commands, e-mail: [email protected]
>

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to