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.

Reply via email to