Hi Michał,
Inline below.
On 16/02/2022 5:24 pm, Michał Kłeczek wrote:
On 16 Feb 2022, at 04:25, Peter Firmstone <peter.firmst...@zeus.net.au> wrote:
From the CodebaseAccessor service.
The CodebaseAccessor proxy (local code) is passed as a parameter along with a
MarshalledInstance of the proxy, by ProxySerializer to ProxyCodebaseSpi.
https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/net/jini/export/CodebaseAccessor.java
<https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/net/jini/export/CodebaseAccessor.java>
Ok, so you have introduced a level of indirection to retrieve String codebase
annotations. Why not go one step further and instead of:
interface CodebaseAccessor {
String getClassAnnotation() throws IOException;
}
have something along the lines of (conceptually):
interface Codebase extends RemoteMethodControl {
ClassLoader getClassLoader() throws IOException;
}
I personally wouldn't take this step, because CodebaseAccessor is a
Remote interface and ClassLoader is a class of high privilege, so it
presents a security risk. It is also used in IPv6 multicast lookup
discovery, where I want to avoid sending any objects over the network.
If you want to send java bytecode, you could do that by converting the
byte[] array to a String, then convert it back to byte[], that way you
can leverage existing api. The problem is that you are still
constrained by the limitations I have chosen, that is, to use
CodebaseAccessor requires the export of a remote object. I think you
wish to be able to send arbitrary graphs, so you will need to append
these bytes in the stream, rather than use CodebaseAccessor.
The reasoning is:
In principle class annotation is a program in (unspecified) language that is
executed by the client to create a ClassLoader instance.
You have to make sure all participants share this language and can execute it
in the same way.
If the type of class annotation is String - it only means this is some kind of
obscure interpreted scripting language.
The insight here is that we _already have_ the language that we _know_ all
participants can execute: Java bytecode.
There is no reason not to use it.
Ok, so the client has to know in advance how to load the service code?
Yes, the client knows how to load the service code dynamically just prior to
proxy unmarshalling, a default ProxyCodebaseSpi is provided for that purpose
and it is used by default.
See above:
you can move this code from the client to the service and let it provide the
implementation of class resolution algorithm.
I would advise against that, the remote service JVM knows little about
the client JVM, both are likely to have completely different ClassLoader
hierarchies.
What I try to do is have identical bundles loaded by the ClassLoaders at
the ServerEndpoint and (client) Endpoint and let OSGi resolve the
classes, so they both have an absolutely identical view of classes. At
the very least, dependency bundles resolved need to be version
compatible for serialized form. Same with other modular frameworks.
If you are using Maven, eg Rio Resolver, or OSGi, then a ProxyCodebaseSpi
implementation specific to one of these should be used. If you have a mixed
environment, then you can use the codebase string to determine which to use.
See above: you can also abstract it away behind an interface.
Does it require to have it installed in advance? If so - how?
Only JGDMS platform and JERI.
How are service proxy classes loaded then?
ProxyCodebaseSpi::resolve does this by provisioning a ClassLoader then
unmarshalling the proxy into it.
OSGi:
https://github.com/pfirmstone/JGDMS/blob/a774a9141e6571f1d7f9771f74b714850d447d3e/JGDMS/jgdms-osgi-proxy-bundle-provider/src/main/java/org/apache/river/osgi/ProxyBundleProvider.java#L131
<https://github.com/pfirmstone/JGDMS/blob/a774a9141e6571f1d7f9771f74b714850d447d3e/JGDMS/jgdms-osgi-proxy-bundle-provider/src/main/java/org/apache/river/osgi/ProxyBundleProvider.java#L131>
[...]
I am asking about something different - the smart proxy class depends on _two_
interfaces:
RemoteEventListener <—— proxy class ——> JavaSpace
RemoteEventListener is its service interface.
But it is not known in advance what interfaces the client already has:
1) Just RemoteEventListener
2) Both RemoteEventListener and JavaSpace
3) None
How is class resolution implemented in JGDMS so that it works properly in _all_
of the above cases?
This is a responsibility of the underlying platform used for class resolution
or modularity.
If using OSGi, OSGi will resolve the required dependencies and download them if
not already present on the client, OSGi will give preference if a compatible
version of the bundle dependencies already loaded at the client. If using
preferred classes the preferred class list will determine the order of
preference, whether classes are loaded from the proxy codebase or the client
ClassLoader (the parent loader of the proxy ClassLoader) first.
I also tried this route and it is a dead end because
* it is not possible to statically (ie. as part of the software package like
OSGi manifest) provide dependency resolution constraints to be able to exchange
arbitrarily constructed object graphs *
This is a limitation and compromise I have accepted, JGDMS doesn't
attempt to load arbitrarily constructed object graphs, instead it
ensures that both endpoints of a Service have the same class resolution
view, the same proxy bundle version is used at the Server and client.
At the server, the proxy bundle is depended upon by the service
implementation, but nothing at the client depends upon the proxy bundle
loaded there, instead the proxy bundle depends on the api loaded by the
client. That is why JGDMS discourages marshaling of client subclasses
that override service method parameter classes, because they cannot be
exported as remote objects and are subject to codebase annotation loss
and class resolution problems. Sometimes, less is more, I've chosen
this compromise in this instance to avoid complexity. I saw little to
be gained from the added complexity, it can be worked around by
improving service api design and practices. Instead the client can
export method parameter interface types as remote objects, with
independent ClassLoader visibility, that will resolve to common super
interface types.
If there is a versioning problem at the client, where it uses an
incompatible API with the client, ServiceDiscoveryManager will recognise
the service is the incorrect type and discard it.
I recognise my own limitations, I'm not smart enough to solve the
problems of de-serialization of arbitrary object graphs, so I have left
it as a task for someone either smarter or more determined than myself
and focused on what I believe is an acceptable and reliable compromise.
It is because of my own limitations, that I reduced complexity, so that
I (as well as inexperienced users) didn't have to worry about codebase
annotation loss or class resolution issues.
I'm not saying it can't be solved, or that you won't succeed, simply
that after a lot of thought, I decided it would be easier to reduce
complexity and accept some limitations as this seems like the best
compromise, there are also other things which have a higher priority for
my time.
Cheers,
Peter.
Take for example the RemoteEventSpacePublisher class from my previous email:
interface RemoteEventListener {
}
package net.jini.space;
interface JavaSpace {
}
Bundle manifest:
Import-Package: net.jini.space;version=“[1.0,1.7)”
//implements JavaSpace up to version 1.7
class JavaSpaceImplProxy implements JavaSpace, Serializable {
}
Bundle manifest:
Import-Package: net.jini.space;version=“[1.5,2.0)”
//Requires JavaSpace of at least version 1.5
class RemoteEventSpacePublisher implements RemoteEventListener, Serializable {
private JavaSpace space;
}
During deserialisation of RemoteEventSpacePublisher the resolution of a bundle
containing JavaSpace interface has to result in a _single_ bundle that provides
net.jini.space package obeying _two_ constraints:
Import-Package: net.jini.space;version=“[1.5,2.0)” - from
RemoteEventSpacePublisher
Import-Package: net.jini.space;version=“[1.0,1.7)” - from JavaSpaceImplProxy
The problem is that this information is not known to the client OSGi runtime:
1. It first resolves RemoteEventSpacePublisher annotation to a bundle wired to
some bundle providing net.jini.space;version=“[1.5,2.0)”
2. After that it _separately_ resolves JavaSpaceImplProxy annotation to a
bundle wired to _possibly different_ bundle providing
net.jini.space;version=“[1.0,1.7)”
The reason is that when serialising the object graph in your solution you are
loosing information about effective runtime constraints used for class
resolution.
Your ProxyBundleProvider interprets codebase annotation as a path to a specific
bundle - but it does not contain any information about this bundle wiring in
the service VM.
If using Rio Resolver, it will downloaded any required dependencies dynamically
using Maven.
I am afraid it does not change anything.
https://github.com/pfirmstone/JGDMS/wiki/OSGi-and-JGDMS
<https://github.com/pfirmstone/JGDMS/wiki/OSGi-and-JGDMS>
What I learned from the many discussions and arguments on River mailing lists,
was that developers use different platforms to manage class resolution
visibility or provide modularity, so I considered it important not to constrain
developers and allow them to use their preferred platform, not to decide for
them.
The point is that _none_ of the existing platforms can handle it properly (see
above).
Once you solve the problem of exchanging arbitrarily constructed object graphs
of dynamically downloaded and resolved classes it opens up a whole lot of
possibilities because you can quite easily model solutions as POJOs.
Cheers,
Michal