Why application server interoperability
                  should be based on smoke signals

<vendor>

Recently there was a raging debate on the EJB-Interest mailing list
concerning whether interoperability among application servers should
be provided by a protocol-based solution or an API-based solution.
There were some compelling arguments on either side with, for the
most part, AppServer providers who already built their systems on IIOP
arguing for a protocol-based solution, and AppServer providers who
built their systems primarily on other protocols arguing for an
API-based solution.

One of the more compelling arguments from the "anti-IIOP" group was
the fact that protocol-based solutions have failed, in that various
CORBA vendors have failed to provide interoperability among their
products using IIOP.  Thus, the argument goes, a different approach
(based on APIs instead of protocols) should be taken.  There is
evidence, they note, coming out of the JINI community that an
API-based solution which relies on Java class downloading and the
ubiquity of Java Virtual Machines will provide universal connectivity
among servers.

I would like to make the argument that API-based solutions have
failed, in that various EJB vendors have failed to provide products
which comply with the RMI APIs.  Thus, I would argue, a different
approach (based on smoke signals instead of APIs) should be taken.
There is evidence coming out of the Native American community that a
smoke signal based solution which relies on the ubiquity of fire will
provide universal connectivity among servers.

The critical reader will observe that I am not actually arguing for
the use of smoke signals, but pointing out a flaw in the anti-IIOP
case: it is not generally the specification that fails (in one case,
IIOP, in the other RMI) but rather it is the vendors who fail, in that
they fail to provide correct implementations of the specifications.

In the case of IIOP, the failure is primarily due to the fact that one
high-profile vendor has, over the course of the last few years,
continuously released products which fail to support the IIOP
specification completely and correctly.  Unfortunately, when the
customer defines the "success" of IIOP as the ability to interoperate
between this non-compliant product and any other product, then clearly
IIOP is doomed to "fail".

But, again, I would argue that this "failure" has nothing to do with
the fact that interoperability was defined at the protocol level,
rather than at the API level.  The failure had to do with a lack of
compliance with the specification.  Changing to an API-based solution
does not address the key issue, which is compliance.

An API-based solution is just as prone to failure, if the definition
of success (or failure) is based on the compliance (or lack of
compliance) of products to the specification.  To make the point, I
will show that even an API-based solution can "fail", using this
definition of failure.

I have developed a fairly straight-forward RMI API compliance checker.
This test provides a simple stateless session bean, which is to be
deployed into the AppServer under test.  Then, a client is run against
the AppServer which tests various aspects of the RMI API.  (Note that
this test is for the RMI APIs, not for RMI protocol, which might be
RMI-over-JRMP, RMI-over-IIOP, or RMI over any other protocol.  Any
proper implementation of the RMI APIs should pass all tests.)

What we discover after running the tests on a half a dozen different
products is that some products comply with the RMI APIs, and some do
not.  Two AppServers have excellent compliance, passing 100% of the
tests.  One AppServer (or actually, two different versions of the same
server) does very well, passing all but one, or 99.3% of the tests.
Two AppServers do quite poorly, one passing only 70.5% of the tests,
and the other passing just 58.3% of the tests.

Thus, I could make the argument that API-based solutions have failed,
and we should look for some other solution, such as smoke signals.  Of
course, it should be obvious that there is nothing broken about the
RMI APIs: what is broken are some of the products.

Likewise, with protocol-based solutions.  The fact that not all
vendors in the CORBA community provide correct IIOP implementations
does not mean that IIOP has failed.  It is simply those products that
are failing to provide compliance.  As such, IIOP is still very much a
viable technology when it comes to providing interoperability among
applications servers.

A quick note to the anti-IIOP camp: please note that in the above
argument, I do not attempt to prove that protocol-based solutions are
better or worse than API-based solutions.  I am simply arguing that a
protocol-based solution (namely IIOP) is completely viable.

Finally, I should note that we were only able to run the RMI
compliance checker on a few of the AppServers currently on the market.
I imagine people would be interested to know how other products stack
up.  For example, I would be curious to know how the tests run on the
following AppServers:

        BEA's WebLogic Enterprise
        BEA's WebLogic Server running over IIOP
        IBM's WebSphere
        Oracle's Application Server
        SUN/AOL/Netscape's iPlanet

Please, post your results for these and other products.

</vendor>

-jkw

Appendix A: Building and running the test
=========================================

The test should be fairly easy to get running.  It uses only a
stateful session bean which is EJB 1.0 or EJB 1.1 compliant.  The code
should run on JDK 1.1.x or JDK 1.2.x.  It is really very simple code.

To build and run the tests with IAS4, put the attached files in the
requisite directories, and use the commands:

% vbjc *.java
% java2iiop -compile RmiTestHome
% jar cMf rmi_test_beans.jar META-INF *.class

On the server, use the command:

% vbj com.inprise.ejb.Container test rmi_test_beans.jar -jns -jss

On the client, use the command:

% vbj RmiTestClient

To investigate where the tests are failing, use the following to
invoke the client:

% vbj -Ddebug RmiTestClient

To get the tests running on other EJB containers, you will have to
modify the deployment descriptor.  You may also have to remove methods
from the remote interface, if certain parameter types are not
supported.  Currently, the remote interface includes the following
types:

  int
  int[]
  java.lang.String
  java.lang.String[]
  java.lang.Object
  java.lang.Object[]
  java.util.Vector
  java.rmi.Remote
  javax.ejb.EJBObject
  javax.ejb.EJBHome
  RmiTest       // the bean's remote interface
  RmiTestHome   // the bean's home interface

Although all of these are standard types that any EJB implementation
should support, some products may not support all the types.  If so,
remove the methods from the remote interface (RmiTest.java) and also
remove the corresponding calls from the client (RmiTestClient.java).
The test will know to calculate the score correctly, even with these
changes.

Appendix B: Test results
========================

Results of running the RMI compliance test on a number of different
servers.

-----------------------------------------------------------------------
Sun's Reference Implementation

Support for int         PASSED
Support for int[]       PASSED
Support for String      PASSED
Support for String[]    PASSED
Support for EJBObject   PASSED
Support for EJBHome     PASSED
Support for Vector(1)   PASSED
A nonfatal internal JIT (3.10.107(x)) error 'GetRegisterA' has occurred...
Support for Vector(2)   PASSED

    Ran 156/156 tests:  100.0%
Skipped   0/156 tests:  0.0%
 Passed 156/156 tests:  100.0%
 Failed   0/156 tests:  0.0%
-----------------------------------------------------------------------

-----------------------------------------------------------------------
Inprise Application Server version 4.0

Support for int         PASSED
Support for int[]       PASSED
Support for String      PASSED
Support for String[]    PASSED
Support for EJBObject   PASSED
Support for EJBHome     PASSED
Support for Vector(1)   PASSED
Support for Vector(2)   PASSED

    Ran 156/156 tests:  100.0%
Skipped   0/156 tests:  0.0%
 Passed 156/156 tests:  100.0%
 Failed   0/156 tests:  0.0%
-----------------------------------------------------------------------

-----------------------------------------------------------------------
BEA's WebLogic Server version 4.51

Support for int         PASSED
Support for int[]       PASSED
Support for String      PASSED
Support for String[]    ** FAILED **
Support for EJBObject   PASSED
Support for EJBHome     PASSED
Support for Vector(1)   PASSED
Support for Vector(2)   PASSED

Ran     156/156 tests:  100.0%
Skipped   0/156 tests:  0.0%
Passed  155/156 tests:  99.3%
Failed    1/156 tests:  0.6%
-----------------------------------------------------------------------

-----------------------------------------------------------------------
BEA's WebLogic Server version 5.0.0b2 running over T3

Support for int         PASSED
Support for int[]       PASSED
Support for String      PASSED
Support for String[]    ** FAILED **
Support for EJBObject   PASSED
Support for EJBHome     PASSED
Support for Vector(1)   PASSED
Support for Vector(2)   PASSED

    Ran 156/156 tests:  100.0%
Skipped   0/156 tests:  0.0%
 Passed 155/156 tests:  99.3%
 Failed   1/156 tests:  0.6%
-----------------------------------------------------------------------

-----------------------------------------------------------------------
Persistence's PowerTier 5.12

Support for int         PASSED
Support for int[]       ** FAILED **
Support for String      ** FAILED **
Support for String[]    PASSED
Support for EJBObject   ** FAILED **
Support for EJBHome     PASSED
Support for Vector(1)   ** FAILED **
Support for Vector(2)   PASSED

    Ran 120/156 tests:  76.9%
Skipped  36/156 tests:  23.0%
 Passed 110/156 tests:  70.5%
 Failed  10/156 tests:  6.4%
-----------------------------------------------------------------------

-----------------------------------------------------------------------
IONA's iPortal

Support for int         PASSED
Support for int[]       PASSED
Support for String      ** FAILED **
Support for String[]    PASSED
Support for EJBObject   ** FAILED **
Support for EJBHome     ** FAILED **
Support for Vector(1)   ** FAILED **
Support for Vector(2)   PASSED

    Ran 101/156 tests:  64.7%
Skipped  55/156 tests:  35.2%
 Passed  91/156 tests:  58.3%
 Failed  10/156 tests:  6.4%
-----------------------------------------------------------------------
// RmiTest.java

import java.io.*;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;

public interface RmiTest extends EJBObject {

  int            test_int           (int            x) throws RemoteException;
  int[]          test_ints          (int[]          x) throws RemoteException;
  String         test_String        (String         x) throws RemoteException;
  String[]       test_Strings       (String[]       x) throws RemoteException;
  Object         test_Object        (Object         x) throws RemoteException;
  Object[]       test_Objects       (Object[]       x) throws RemoteException;
  Vector         test_Vector        (Vector         x) throws RemoteException;
  Remote         test_Remote        (Remote         x) throws RemoteException;
  EJBObject      test_EJBObject     (EJBObject      x) throws RemoteException;
  EJBHome        test_EJBHome       (EJBHome        x) throws RemoteException;
  RmiTest        test_RmiTest       (RmiTest        x) throws RemoteException;
  RmiTestHome    test_RmiTestHome   (RmiTestHome    x) throws RemoteException;

}
// RmiTestHome.java

public interface RmiTestHome extends javax.ejb.EJBHome {

  RmiTest create()
    throws java.rmi.RemoteException, javax.ejb.CreateException;

}
// RmiTestBean.java

import java.io.*;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;

public class RmiTestBean implements SessionBean {

  public int test_int(int x) {
    return x;
  }

  public int[] test_ints(int[] x) {
    return x;
  }

  public String test_String(String x) {
    return x;
  }

  public String[] test_Strings(String[] x) {
    return x;
  }

  public Object test_Object(Object x) {
    return x;
  }

  public Object[] test_Objects(Object[] x) {
    return x;
  }

  public Vector test_Vector(Vector x) {
    return x;
  }

  public Remote test_Remote(Remote x) {
    return x;
  }

  public EJBObject test_EJBObject(EJBObject x) {
    return x;
  }

  public EJBHome test_EJBHome(EJBHome x) {
    return x;
  }

  public RmiTest test_RmiTest(RmiTest x) {
    return x;
  }

  public RmiTestHome test_RmiTestHome(RmiTestHome x) {
    return x;
  }

  public void setSessionContext(SessionContext unused) {
  }

  public void ejbCreate() {
  }

  public void ejbRemove() {
  }

  public void ejbActivate() {
  }

  public void ejbPassivate() {
  }

}
// RmiTestClient.java

import java.io.*;
import java.rmi.*;
import java.util.*;
import javax.ejb.*;
import javax.naming.*;
import javax.rmi.*;

public class RmiTestClient {

  final static boolean debug = System.getProperty("debug") != null;

  static RmiTest rmiTest;

  static int testsRun    = 0;
  static int testsPassed = 0;
  static int testsFailed = 0;
  static int testsTotal  = 156;

  private interface Verifier {
    void verify(Object arg, Object result) throws Exception;
  }

  public static void main(String[] args) throws Exception {

    // get a JNDI context...
    // you may have to modify this code if the given server
    // does not support JNDI 1.2, which should allow the
    // naming context to be created using the initial context's
    // default constructor...
    Context context = new InitialContext();

    RmiTestHome rmiTestHome = (RmiTestHome)
      PortableRemoteObject.narrow(context.lookup("rmiTest"), RmiTestHome.class);

    rmiTest = rmiTestHome.create();

    int beforeTestsFailed = testsFailed;
    try { // testing int
      // we pass various values for int, and make sure the server sends
      // back the same value.  If an AppServer can't do this, it is
      // likely misinstalled or misconfigured.
      assert(rmiTest.test_int(0) == 0);
      assert(rmiTest.test_int(1) == 1);
      assert(rmiTest.test_int(-1) == -1);
      assert(rmiTest.test_int(Integer.MIN_VALUE) == Integer.MIN_VALUE);
      assert(rmiTest.test_int(Integer.MAX_VALUE) == Integer.MAX_VALUE);
    }
    catch(Throwable e) {
      error(e);
    }
    message("int ", beforeTestsFailed);

    beforeTestsFailed = testsFailed;
    try { // testing int[]
      // construct an array of ints, and send over various values.
      // then check that what gets sent back has the same set of
      // values
      int[] before = { 0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE };
      int[] after = rmiTest.test_ints(before);
      assert(before.length == after.length);
      for(int i = 0; i < before.length; i++) {
        assert(before[i] == after[i]);
      }
      // see if the null array is supported, as required by RMI
      assert(rmiTest.test_ints(null) == null);
    }
    catch(Throwable e) {
      error(e);
    }
    message("int[]", beforeTestsFailed);

    beforeTestsFailed = testsFailed;
    try { // testing String
      // see if just a string is supported
      assert(rmiTest.test_String("Hello").equals("Hello"));
      // see if the null string is supported
      assert(rmiTest.test_String(null) == null);
      // see if Unicode strings are supported
      assert(rmiTest.test_String("\u4e2d\u56fd").equals("\u4e2d\u56fd"));
    }
    catch(Throwable e) {
      error(e);
    }
    message("String", beforeTestsFailed);

    beforeTestsFailed = testsFailed;
    try { // testing String[]
      {
        // see if we can send an array of strings, including the
        // null string
        String[] before = { "Hello", "World", null };
        String[] after = rmiTest.test_Strings(before);
        assert(before.length == after.length);
        for(int i = 0; i < before.length; i++) {
          // make sure each value is either pointer equivalent
          // or value equivalent.  Either will do.
          assert(before[i] == after[i] || before[i].equals(after[i]));
        }
      }
      {
        // if we send multiple instances of the same string in an
        // array,  then when we get it back, the parameters should
        // still be pointer equal
        String s = "Hello";
        String[] before = { s, s };
        String[] after = rmiTest.test_Strings(before);
        assert(before.length == after.length);
        // object identity should be preserved in RMI
        assert(after[0] == after[1]);
      }
    }
    catch(Throwable e) {
      error(e);
    }
    message("String[]", beforeTestsFailed);

    beforeTestsFailed = testsFailed;
    try { // testing remote object references
      // here we check that if we pass a reference to a remote object
      // it isIdentical to its original value
      Verifier verifier = new Verifier() {
        public void verify(Object arg, Object result) throws Exception {
          {
            EJBObject a = (EJBObject) PortableRemoteObject.narrow(arg, 
EJBObject.class);
            EJBObject b = (EJBObject) PortableRemoteObject.narrow(result, 
EJBObject.class);
            // make sure the objects are identical...
            assert(a.isIdentical(b));
          }
          {
            RmiTest a = (RmiTest) PortableRemoteObject.narrow(arg, RmiTest.class);
            RmiTest b = (RmiTest) PortableRemoteObject.narrow(result, RmiTest.class);
            // make sure the objects are identical...
            assert(a.isIdentical(b));
          }
        }
      };
      test(rmiTest, verifier);
    }
    catch(Throwable e) {
      error(e);
    }
    message("EJBObject", beforeTestsFailed);

    beforeTestsFailed = testsFailed;
    try { // testing home object references
      // for home references, we can only check that after passing the
      // reference to the server and back, that it still points to the
      // same home class.  There is no equality test for home references.
      Verifier verifier = new Verifier() {
        public void verify(Object arg, Object result) throws Exception {
          {
            EJBHome home = (EJBHome) PortableRemoteObject.narrow(result, 
EJBHome.class);
            
assert(home.getEJBMetaData().getHomeInterfaceClass().equals(RmiTestHome.class));
          }
          {
            RmiTestHome home = (RmiTestHome) PortableRemoteObject.narrow(result, 
RmiTestHome.class);
            
assert(home.getEJBMetaData().getHomeInterfaceClass().equals(RmiTestHome.class));
          }
        }
      };
      test(rmiTestHome, verifier);
    }
    catch(Throwable e) {
      error(e);
    }
    message("EJBHome", beforeTestsFailed);

    beforeTestsFailed = testsFailed;
    try { // testing Vector
      // this is the most complex test: basically, we construct
      // a Vector, and put all sorts of stuff into it.  Then we
      // check that the Vector passed back from the server still
      // contais the same stuff, with all the correct semantics
      // as required by RMI
      Verifier verifier = new Verifier() {
        public void verify(Object arg, Object result) throws Exception {
          Vector vector = (Vector) result;
          assert(vector.size() == 8);
          assert(vector.elementAt(0).equals("Hello"));
          assert(vector.elementAt(0) == vector.elementAt(1));
          assert(vector.elementAt(2).equals(new Integer(1)));
          assert(vector.elementAt(3).equals(new Date(1234)));
          RmiTest tmpRmiTest = (RmiTest)
            PortableRemoteObject.narrow(vector.elementAt(4), RmiTest.class);
          assert(tmpRmiTest.isIdentical(rmiTest));
          RmiTestHome tmpRmiTestHome = (RmiTestHome)
            PortableRemoteObject.narrow(vector.elementAt(5), RmiTestHome.class);
          
assert(tmpRmiTestHome.getEJBMetaData().getHomeInterfaceClass().equals(RmiTestHome.class));
          Hashtable hashtable = (Hashtable) vector.elementAt(6);
          assert(hashtable.keys().nextElement() == vector.elementAt(0));
          assert(hashtable.elements().nextElement() == vector.elementAt(0));
          Object[] array = (Object[]) vector.elementAt(7);
          for(int i = 0; i < array.length; i++) {
            assert(array[i] == vector.elementAt(i));
          }
        }
      };
      Vector vector = new Vector();
      // add an item twice
      String s = "Hello";
      vector.add(s);
      vector.add(s);
      // add a couple different items
      vector.add(new Integer(1));
      vector.add(new Date(1234));
      // add some object references
      vector.add(rmiTest);
      vector.add(rmiTestHome);
      // add a hashtable containing the above string
      Hashtable hashtable = new Hashtable();
      hashtable.put(s, s);
      vector.add(hashtable);
      // finally, add an array containing the already added stuff...
      Object[] array = new Object[vector.size()];
      for(int i = 0; i < array.length; i++) {
        array[i] = vector.elementAt(i);
      }
      vector.add(array);
      test(vector, verifier);
    }
    catch(Throwable e) {
      error(e);
    }
    message("Vector(1)", beforeTestsFailed);

    beforeTestsFailed = testsFailed;
    try { // testing tree of Vectors
      // this test checks that deeply nested complex data retains
      // its validity, when send over the wire.
      Verifier verifier = new Verifier() {
        public void verify(Object arg, Object result) throws Exception {
          Vector vector = (Vector) result;
          for(int i = 0; i < 10; i++) {
            // each parent node should have two identical vectors
            assert(vector.size() == 2);
            assert(vector.elementAt(0) == vector.elementAt(1));
            vector = (Vector) vector.elementAt(0);
          }
          // the last child node should be empty
          assert(vector.size() == 0);
        }
      };
      Vector child = new Vector();
      for(int i = 0; i < 10; i++) {
        Vector parent = new Vector();
        parent.addElement(child);
        parent.addElement(child);
        child = parent;
      }
      test(child, verifier);
    }
    catch(Throwable e) {
      error(e);
    }
    message("Vector(2)", beforeTestsFailed);

    rmiTest.remove();

    System.out.println();
    { // print out the results
      String[] title = {
        "    Ran",
        "Skipped",
        " Passed",
        " Failed",
      };
      int[] value = {
        testsRun,
        testsTotal - testsRun,
        testsPassed,
        testsFailed,
      };
      for(int i = 0; i < title.length; i++) {
        String pad = value[i] < 10 ? "  " : value[i] < 100 ? " " : "";
        System.out.println(title[i] + " " + pad + value[i] + "/" + testsTotal + " 
tests:  " +
                           (1000 * value[i] / testsTotal) / 10f + "%");
      }
    }
  }

  private static void message(String type, int beforeTestsFailed) {
    String outcome = beforeTestsFailed == testsFailed ? "PASSED" : "** FAILED **";
    System.out.println("Support for " + type + "\t" + outcome);
  }

  private static void assert(boolean assertion) {
    if(assertion == true) {
      testsRun++;
      testsPassed++;
    }
    else {
      error(new Error("Test Failed"));
    }
  }

  private static void error(Throwable t) {
    testsRun++;
    testsFailed++;
    if(debug) {
      t.printStackTrace(System.err);
    }
  }

  private static void test(Object arg, Verifier verifier) throws Exception {
    try { // test Object
      Object result = rmiTest.test_Object(arg);
      verifier.verify(result, arg);
    }
    catch(Throwable e) {
      error(e);
    }

    try { // test Object[]
      Object[] args = { arg };
      Object[] result = rmiTest.test_Objects(args);
      assert(args.length == result.length);
      verifier.verify(result[0], args[0]);
    }
    catch(Throwable e) {
      error(e);
    }

    if(Vector.class.isAssignableFrom(arg.getClass())) {
      try {
        Vector result = rmiTest.test_Vector((Vector) arg);
        verifier.verify(result, arg);
      }
      catch(Throwable e) {
        error(e);
      }
    }

    if(Remote.class.isAssignableFrom(arg.getClass())) {
      try {
        Remote result = rmiTest.test_Remote((Remote) arg);
        verifier.verify(result, arg);
      }
      catch(Throwable e) {
        error(e);
      }
    }

    if(EJBObject.class.isAssignableFrom(arg.getClass())) {
      try {
        EJBObject result = rmiTest.test_EJBObject((EJBObject) arg);
        verifier.verify(result, arg);
      }
      catch(Throwable e) {
        error(e);
      }
    }

    if(EJBHome.class.isAssignableFrom(arg.getClass())) {
      try {
        EJBHome result = rmiTest.test_EJBHome((EJBHome) arg);
        verifier.verify(result, arg);
      }
      catch(Throwable e) {
        error(e);
      }
    }

    if(RmiTest.class.isAssignableFrom(arg.getClass())) {
      try {
        RmiTest result = rmiTest.test_RmiTest((RmiTest) arg);
        verifier.verify(result, arg);
      }
      catch(Throwable e) {
        error(e);
      }
    }

    if(RmiTestHome.class.isAssignableFrom(arg.getClass())) {
      try {
        RmiTestHome result = rmiTest.test_RmiTestHome((RmiTestHome) arg);
        verifier.verify(result, arg);
      }
      catch(Throwable e) {
        error(e);
      }
    }
  }

}

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN' 
'http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd'>

<ejb-jar>
  <description>
        This is an example for a Stateless session bean
    </description>
  <display-name></display-name>
  <small-icon></small-icon>
  <large-icon></large-icon>
  <enterprise-beans>
    <session>
      <description></description>
      <display-name></display-name>
      <small-icon></small-icon>
      <large-icon></large-icon>
      <ejb-name>rmiTest</ejb-name>
      <home>RmiTestHome</home>
      <remote>RmiTest</remote>
      <ejb-class>RmiTestBean</ejb-class>
      <session-type>Stateful</session-type>
      <transaction-type>Container</transaction-type>
    </session>
  </enterprise-beans>
  <assembly-descriptor>
    <container-transaction>
      <description></description>
      <method>
        <description></description>
        <ejb-name>rmiTest</ejb-name>
        <method-name>*</method-name>
      </method>
      <trans-attribute>NotSupported</trans-attribute>
    </container-transaction>
  </assembly-descriptor>
  <ejb-client-jar></ejb-client-jar>
</ejb-jar>

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE inprise-specific PUBLIC '-//Inprise Corporation//DTD Enterprise JavaBeans 
1.1//EN' 'http://www.borland.com/devsupport/appserver/dtds/ejb-inprise.dtd'>

<inprise-specific>
  <enterprise-beans>
    <session>
      <ejb-name>rmiTest</ejb-name>
      <bean-home-name>rmiTest</bean-home-name>
      <timeout>0</timeout>
    </session>
  </enterprise-beans>
</inprise-specific>

Reply via email to