Some final thoughts on using OSGi with Jini.
https://www.artima.com/weblogs/viewpost.jsp?thread=202304
This was always a contentious issue historically on Jini lists, I expect
there will be vastly differing opinions on the subject, so I haven't
made any decisions that would constrain developers options to any
particular framework. While I respectfully disagree with most of
Michał's assessments and opinions regarding JGDMS on this occasion, I
haven't made any changes that would prevent him from marshaling Objects,
ClassLoaders or bytecode to replace codebase annotations in streams. I
still respect that Michał has an inquiring mind and is willing to
experiment with some very complex issues.
Work to support OSGi in JGDMS is ongoing and doesn't require developers
to install or use OSGi.
To dispel any confusion that might have arisen as a result of this
discussion, and for anyone reading the email archives, JGDMS avoids
annotating marshaling streams with codebase strings and uses a
CodebaseAccessor service instead, which also provides signer
certificates, not just space separated URI strings, these certificates
may be self signed, they are dynamically granted permission as they have
come from an authenticated service via a secure connection.
CodebaseAccessor is also used to authenticate the service, prior to
unmarshaling its proxy. ProxyCodebaseSpi allows you to use the modular
framework of your choice for the provisioning of ClassLoaders and wiring
of their dependencies, CodebaseAccessor provides information about the
proxy's codebase requirements prior to its unmarshaling. Atomic
marshaling streams make no assumptions about the module structures of
systems at either endpoint. The atomic marshal stream only requires
that identical proxy codebases are loaded at either endpoint, that is
all, so that all classes the service uses will be resolved by the
ClassLoader at the remote endpoint. This is why all services are given
their own independent marshaling streams to manage class visibility and
avoid incorrect class resolution, which results in codebase annotation
loss. This is why I am not using the atomic marshaling framework to
control or dictate ClassLoader hierarchies, and allow it instead to only
focus on marshaling and unmarshaling object bytes securely.
This also solves a long standing issue with unmarshaling in OSGi. Now
OSGi (or other frameworks) can manage class resolution, rather than the
object marshaling framework taking on this responsbility and trying to
replicate the functionality of each or forcing developers to chose.
Marshaling objects has been uncoupled from class resolution in JGDMS.
The ClassLoader is only assigned once to the Endpoint, from that point
on, all class resolution decisions are made by the ClassLoader. This is
unlike Jini and RMI marshalling of codebase annotations, where an
attempt is made for each class to find its ClassLoader using stream
annotations, if present during unmarshaling.
This work has taken many years to complete and a lot of research has
gone into the decision making process.
JGDMS is modular and all jar file artifacts are also OSGi bundles.
AtomicILFactory, uses ClassLoaders at Endpoints, doesnt utilise the
thread context ClassLoader, making it more compatible with modular
frameworks.
JGDMS provides a RFC 3986 compliant URI that supports RFC 5952
normalization of IPv6 addresses, this provides a huge performance
benefit, as DNS calls are not required to check for address equality. I
am still using space separated URI strings for codebases. This URI
class is not serializable, parsing URI strings instead is good security
practice.
https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/org/apache/river/api/net/Uri.java
One of the problems that I am currently working on in the support of
OSGi, is that of proxy identity, there are many proxies that use the
same bundle version, eg all common services, and these have static
fields that shouldn't be shared among global providers. For example
different lookup services from different providers on the internet
shouldn't share implementation classes.
I am currently investigating using Apache Aries and OSGi subsystems, so
that service proxies remain independent and isolated from each other
when utilizing OSGi as the underlying framework only cooperating on
service API. It is a non goal to make incompatible versions of Service
APIs interoperate, for example if someone changes an Entry in a way that
breaks compatibility, then I expect that it will use a different non
compatible service version, and these will be considered different services.
Any assistance from anyone who is familiar with OSGi and Jini, would be
much appreciated.
Regards,
Peter.
On 16/02/2022 9:32 pm, Peter Firmstone wrote:
Hi Michał,
I didn't take it personal, and don't expect you to take it personal
either when I say, I'm pretty sure everyone here is aware of River /
Jini limitations.
Anyway River has been a lot of fun, all the best for the future
everyone, hope to see you around from time to time.
Cheers,
Peter.
On 16/02/2022 7:53 pm, Michał Kłeczek wrote:
Hi Peter,
On 16 Feb 2022, at 10:01, Peter Firmstone
<[email protected]> wrote:
Inline below.
On 16/02/2022 5:24 pm, Michał Kłeczek wrote:
On 16 Feb 2022, at 04:25, Peter Firmstone
<[email protected]> 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.
Not really as implementation is constrained by the same security
rules as any other code: you can constrain it via policy that only
grants create ClassLoader permission to specific implementations.
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.
To be able to communicate they have to understand each other class
loading mechanism anyway.
In particular the class annotation String syntax and semantics has to
be the same for both.
[...]
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.
This breaks once there are multiple parties (services) involved
because it means _all_ of them have to have their software versions
synchronised in advance - which in turn makes the excercise moot: if
you have to do that there is no need for mobile code anymore.
What is really IMHO needed is a practical way of independent
evolution of system components.
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.
There is no subclassing in my example of RemoteEventSpacePublisher.
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.
It cannot. How would you improve RemoteEventListener or JavaSpace API
to avoid these issues?
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.
The whole point of my example is that this is not the issue of
compatibility between client and service interface but it is inherent
to existing class loading mechanism.
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.
It wasn’t my intent to make this really interesting technical
discussion personal but rather try to discuss the limitations of
Jini/River and the possible directions.
I personally find the idea of mobile objects really interesting and
very influential to the way how systems are implemented.
Having a single uniform way of exchanging both code and data opens up
a lot of possibilities.
Think of loggers being services implemented by mobile objects that
can be replaced with new versions easily - log4shell would not be
such a huge nightmare to handle.
What’s more - mobile code is something you are doing anyway - but in
an ad-hoc ways that are incompatible with each other.
Michal