James Grahn wrote:
What is the bigger picture you're concerned with?
Reliability, developers experiencing unexpected run time errors and
compatibility issues in deployed code when using Generics in Service API.
In our earlier discussions I provided an example of how generics can
remain inside compile time boundaries while interacting with a service.
I have an experimental section that can be found on svn.
http://svn.apache.org/viewvc/river/jtsk/skunk/pepe/src/org/apache/river/api/lookup/
I've used a wrapper class that performs type checks, so the client can
use generics safely, this gets rid of most of the boilerplate code
without the compromise in type safety.
If you use a wrapper class with your new javaspace interface using
generics, you can use the existing Javaspace API, and perform type
checks in the wrapper class, this will eliminate the boilerplate code.
Since this sort of wrapper class could be reused by a number of clients,
it might make sense to use annotations, or a wrapper service as Gregg
mentioned.
When the wrapper class receives the return object, it checks it's class
is the same type as the parameter before returning it to the client, if
incorrect it can throw a RemoteException that will be handled by the
client rather than a runtime exception which will cause the client jvm
to exit.
But because it can be difficult to test for generic types in java
syntax, it would appear more logical to weave the check in byte code
when the Generic type has been replace by a specific known cast. So we
throw a RemoteException prior to the cast, rather than a Runtime exception.
Alternatively we can just catch a ClassCastException and treat it the
same as a RemoteException.
Doing so would allow people to develop using Generics in their service
API, without degrading reliability. This doesn't take into account any
incompatibilities that Generics would introduce to services, just type
safety and reliability.
Obviously during lookup, we'd want to ensure we've selected the
implementation of the service we want.
EG:
interface DeliveryService<T> {
<T> deliver();
}
Due to erasure T will be replaced by Object, in all cases, now we might
have a service that delivers Apples, another might deliver Sausages, you
don't want a Sausage if your expecting an Apple.
In this case you'd want an Entry in the lookup service that defines the
expected return value. However a service Entry might be misconfigured
(the risk arises whenever data is duplicated) and return a Papaya
instead of a Sausage.
If the client bytecode performs a type check, the client will be able to
catch an exception
So it's worth asking a question, should we promote the use of Generics
in Service API?
We could require they must explicitly declare a ClassCastException as
well as a RemoteException or IOException and leave the onus on the
client to catch the ClassCastException?
So far there have been the following proposed approaches for handling
Generics over Remote connections:
1. With force of sufficient reasoning for each application, attempt
to avoid incorrect type casts.
2. Provide a wrapper class for a service (Gregg's also made a similar
suggestion), to perform type conversions over compile time boundaries.
3. Provide a tool to weave class cast checks that throw a subclass of
RemoteException.
4. Require a Service that contains Generics to declare it throws
ClassCastException, even though this is a runtime exception.
4 is ok for the return values, but what about Generic parameters? On
the service side, the server would need to catch a ClassCastException
and return a RemoteException to the client if it needs anything other
than Object.
The Service would need an Entry describing Generic parameter types it
accepts?
The Service and it's smart proxy were compiled together, so they are
type compatible, however the client and smart proxy were compiled
separately, this is exactly where ClassCastExceptions will occur with
compiler generated type casts when the proxy and client interact. A
ClassCastException will cause a runtime exception in the client JVM, it
will exit. It could potentially be used for DOS attacks.
The Service API contract between the client and proxy will have a degree
of uncertainty when using Generics, it's a vague contract that's missing
a vital piece of information due to erasure.
This degree of uncertainty doesn't exist in an interface lacking
Generics. Note the Service API doesn't need to extend Remote, it can be
a local interface.
This is an interesting exercise, but I do wonder what exactly it is we
gain by using Generics in Service API?
It's worth noting that Generics are not supported in arrays either,
developers readily accept that design decision, not all developers
appear ready to accept the limitations Generics present with separately
compiled code.
This is a limitation of the Java language rather than a limitation with
River.
I'm not convinced we should promote the use of Generics in Service API,
but I encourage James to continue with his experiment and perhaps even
release an implementation of Generic JavaSpaces, this could become a
subproject if there's enough interest. In James case JavaSpace can
infer the ambiguous Generic information via the template parameter, but
the client must trust the JavaSpace service to return the correct type
or be prepared to catch a ClassCastException.
Remember this is our first release of River that uses Java 5 language
features, so we must carefully consider their application. Generics
also introduce additional restrictions evolving API, while retaining
backward compatibility.
I apologise in advance, I won't be able to continue this conversation
due to external time pressures.
Peter.