Avoiding unmarshalling errors with RMI.

Discussions

EJB design: Avoiding unmarshalling errors with RMI.

  1. Avoiding unmarshalling errors with RMI. (9 messages)

    Hi all,

    I've got an interesting thing I'd like to share with you, hoping for a public discussion on this subject.

    I've got EJB's running at my own servers, offering their services to other companies.
    I document the EJB's and hand out those javadoc's to clients (other companies).
    They download my .jar file (containing the interfaces and
    implementations of the EJB's and the objects they return and take as arguments) and make client programs (such as webapps).

    Since those clients are maintained by others, I cannot update their .jar files if I change the implementation of an object that travels between EJB and client.
    So everytime I want to change something (adding a method to a remote interface, changing implementation of objects that travel over RMI) the clients get unmarshalling errors.

    The only way to restore correct RMI communication is to phone the companies, telling them to immediately download a new .jar and restart their client apps.

    Now that seems a bit harsh. I didn't <I>change</I> existing method definitions. Only added a new one.
    I know it is possible (even recommended) to let the client download implementations (through codebase) dynamically, but that's not a complete solution:

    If I add method definitions to a remote EJB interface and restart my applicationserver, the clients still need a restart (or they'd still be using the previously downloaded remote interface and skeleton). Only a restart makes them download the new classes.

    Another point are the objects that are used as return types and arguments. Currently the clients can instantiate those objects locally (they're in the .jar) and then pass them as an argument to an EJB. What to do when I want to change its implementation?

    Is RMI capable of dealing with this situation, or instead should I abandon the concept of sending code over RMI and send only data (to be parsed at the other side).

    That would lead me to conventional solutions such as establishing a TCP connection between server and client, sending only data over it and having the client interpretate it and build a local object with it.
    This way, when I add something on the server side and end up sending more data over the line, it is up to the client to just ignore the extra data and continue building local objects as before.

    Even though it's very basic and conventional and it totally ignores the features of RMI, it seems the only solution flexible enough to survive implementation changes between server and client.


    My question to you guys is to provide me with ideas or solutions for this problem :)

    tia,
    Erik van Zijst
  2. Erik,
    If you want a thorough and quick answer for this one, I would post it to the RMI-USERS at java dot sun dot com mailing list. There are a multitude of RMI experts monitoring that list, including members of the team who wrote RMI.

    Chris
  3. Thanks Chris,

    I just mailed it. If I get any useful responses out of that I'll post them here.

    Erik
  4. I promised to keep theserverside.com informed, so here's a suggestion from RMI-USERS at java dot sun dot com

    <QUOTE>
    Date: Fri, 18 Aug 2000 09:54:35 +0530
    Reply-To: Vivake Panagariya <vivakep at yahoo dot com>
    From: Vivake Panagariya <vivakep at yahoo dot com>
    Subject: Re: Avoiding unmarshalling errors with RMI.
    Comments: To: Erik van Zijst <erik at MARKETXS dot COM>
    Content-Type: text/plain; charset="iso-8859-1"

    I am not very familier with EJB solutions but regarding RMI can offer you this advice.
    See if it can work out in your applications.
    1. Maintain the client on the server along with the other applications.
    2. Distribute a small application ( a bootstrap) to the clients from which they can remotely load the appropriate class. As rmi is very strong in security you can prevent unauthorized access.
    3. This bootstrap application would be generic in nature and may be used with all client appls.
    4. Whenever you want to change a client appl. just make the appropriate changes on the server so the clients can be upgraded and modified without having to install them each time on the client machines.
    But all this is possible only if you are willing to store all client appl. on your server as it would also be expensive in terms of time and server resources.

    Vivake
    </QUOTE>

    Erik
  5. Chris,

    How can I get other mailing list from the Sun ?

    Thank you.
  6. All of Sun's java mailinglists can be found online at:

    http://archives.java.sun.com/archives/index.html

    Posting to them is public, but is managed by moderators.

    Erik
  7. Hi all,

    Another suggestion from Sun (by Adrian Colley):

    <quote>
    You could have the client use a URLClassLoader to load its classes from a set of more central points, instead of keeping the classes locally. You'd need a bootstrap on the client, but the bulk of the code could be got from the network every time.

    If you don't want to do that on every application start, you could simply check the timestamp/version number of the local jar file against the remote (latest) one, and only do the download if it's newer.
    </quote>

    So far, that seems to be the most transparent solution with no impact on client source code.

    Erik
  8. Hi all,

    Earlier I reported unmarshalling trouble with my EJB apps. I posted this to several online forums including this one.
    I'm happy to report that I found a very acceptable solution myself and I'd like to share that with you.

    I'll start by sumarizing my original problem:

    <SUMMARY>
    I have EJB's running on my servers and give their RemoteInterfaces + javadocs + argument/return-type objects to my customers.
    They download this and develop their client apps (usually webapps).

    Shit hits the fan when I update remote EJB interfaces, EJB implementations and (most of all) implementation of objects passed as arguments to/from EJB's.
    Since client apps need to be able to instantiate these themselves I cannot just provide them with interfaces. The result is a bunch of UnmarshalExceptions as soon as I add some functionality to them.

    Same for EJB remote interfaces since they're in the .jar too.

    When I change implementation, I must create a new .jar file which all customers need to download before proceding.
    </SUMMARY>

    My solution.
    Of course I solved the issue with outdated stubs by letting RMI clients retrieve the stubs at runtime through java.rmi.server.codebase.
    They do have the remote interfaces locally though (necessary for compiling), but as long as the EJB keeps its original methods, there's no problem; the new stub still implements the old interface.

    The argument-objects were a different ballgame though. Since client need to be able to instantiate them, they need them locally on the filesystem. To avoid unmarshall exceptions I hardcoded serialVersionUID = 1 so client and server won't detect my serverside changes.

    In order not to break serialization I implemented Externalizable and implemented the writeExternal method so that all it writes to ObjectOutput is a HashMap with the object's fields. Afterall, there's no object flying over the wire, just passive data.
    My readExternal then reads the only object being sent: the HashMap. The fields are restored by calling the appropriate get methods:

    public void writeExternal(ObjectOutput out) throws IOException
    {
    HashMap map = new HashMap();
    map.put("field1", field1);
    map.put("field2", field2);
    out.writeObject(map);
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException
    {
    HashMap map = (HashMap)in.readObject();
    field1 = (String)map.get("field1");
    field2 = (String)map.get("field2");
    }

    This way I can have a different version of the class on the client and server and still make serialization possible.

    When I add methods and field to the object on the server, but my customers don't download it, and such an object is sent from server to client, the client gets a larger HashMap as before, but it just get()'s its own fields.

    Opposite order works similarly. When a get() on the HashMap returns null (field not present), the field just becomes null.

    As long as I don't remove existing methods, I can do whatever I see fit with both beans and argument types without notifying my customers.
    They can on thier turn upgrade their downloaded .jar with a new version anytime they like without any problems.

    Just wanted to share this with you guys.

    P.S.
    To make this hugh message even bigger, I'd like to point out one other thing. When I upgrade EJB implementation (including the stub) the client apps need a restart or they'll continue using the previously downloaded stub and receive an UnmarshalException on the next EJB invoke.
    Is there a way to catch the UnmarshalException and in the scope of this catch let the ClassLoader download the new stub so the application can keep running?

    Erik
  9. For sending data try using a metadata approach such as XML or HashMaps if you need independance of interface. Sending behaviour is another problem, Java 1.3 offers dynamic proxies for assembling classes on the fly, of course Java 1.2 is more widely deployed. If you application performs specific tasks you may need to create a mini XML based language or rule engine. I'm currently working on a scheme for registering new classes via JNDI and codebasing out of HTTP servers. For this you need to move away from the single server approach and towards a more distributed services approach where the components are dynamically loaded, or interface via HTTP etc..

    Rob
  10. Rob,

    Basically what you're saying is to stop sending my own objects over RMI but instead put key-value pairs in hashtables, sending them from server to client.
    Now that certainly has a big impact on the current system.

    Could you tell me some more about the systemm you are working on? You say you register classes via JNDI. Does that mean you can use remote paths in your client classpath and dynamically download every type of class, besides remote object implementations as RMI does?

    Erik