Now Java (including EJB programmers) tend to fully exploit the Java features. So expect lots of Java objects, collection classes etc in remote interfaces. For all these cases it's going to be hairy anyway. If you want to design for cross-language, you may start with IDL, generate Java from it and use the Java signatures in the remote interfaces.
One alternative is to implement a Corba to EJB adaptor. That is a middle layer that takes Corba calls, and
delegates to respective EJB beans. Here is a customer story …
Our Corba IDL interface is a meta-data driven interface. That is, we use tags/value pairs to indicate the session bean to be called, the method on the session bean, and arguments. Really cool stuff, as we no longer have to change IDL when there is a change in a session bean interface, or even Corba client interface. The interface is data driven, just like XML. If you are coming in from a Corba client, then define a simple IDL with tag/value structures for commands and responses. And establish a contract between client and server on what is expected on those tags (maybe something like an XML dtd). Develop a parser on the server side to honor the contract, and have the parser delegate calls to respective beans. Some of your tags could be a hint to what bean and method is going to be called. We have made some of our tags to be the same as the fully qualified session bean class name so that we can use introspection in the corba-ejb adaptor.
Thus, EJBs which are to be used by C++ clients will want to expose interfaces which are easily mapped to C++. This means avoiding "complex" data types (Hashtables, Vectors, etc) and sticking with the simpler, IDL/C++ friendly data types (primitives, strings, arrays, object references).
If one has a predefined EJB which is to C++-friendly, then one can simply write a thin layer which exposes a friendly interface, which calls in to the Java-heavy API. This is very similar to the standard technique of using a session bean to provide a remote-access friendly interface to entity beans, instead of accessing the entity bean directly from a remote client.
Note that with OBV it should definitely be possible to have java.util.Hashtable in C++. This is not a concern. The issue is that datatypes guaranteed to be available on the java platform (such as java.util.Hashtable) are not supplied (ie. automatically available for use by our users) with C++. The user would have to implement their C++ equivalent for java.util.Hashtable. It is just not available out of the box.
So, this feature is not supported in VBJ 4.0 and hence not in IAS. We may be providing such support in a subsequent release.
public Employee getEmployee(long employeeID) throws RemoteException;
Employee is an interface. In the bean, I populate and return an object of class EmployeeState. EmployeeState
implements Employee.
I separated the classes that I thought I would need on the client side, and I did not include the EmployeeState class. I thought that having the Employee interface on the client would be sufficient, as it is in the RMI world. I believe the class file of the return parameter would be marshaled to the client upon request... but that was not so.
(1) to extend all RMI server objects from PortableRemoteObject instead of UnicastRemoteObject,
(2) use the COSNaming implementation of JNDI, and of course
(3) recompile to get RMI-IIOP ties and stubs.
This gives you an "RMI" server object which you can invoke methods on from a CORBA object, a CORBA-based EJB, or (using the dual stub feature of the rmic compiler) even from a classic RMI/JRMP client. If the RMI-IIOP server gets an object reference to another RMI-IIOP server object, it must use PortableRemoteObject.narrow() instead of a Java cast to downcast the reference returned by a JNDI lookup.
The only problem areas are
(1) value types have to be serializable (this is the "OBV subset" problem),
(2) naming problems can occur with overloaded methods (name-mangling restrictions and the standard CORBA interface inheritance restriction),
(3) although type/variable names differing only in case are supported, some other names differing only in case may not be unique,
(4) there's no remote GC in RMI-IIOP, so any code using those interfaces will break, and
(5) code references to RMISocketFactory and Unreferenced will break.
Other than this (which is surprisingly little), porting a large RMI app to RMI-IIOP should be fairly easy. There's also a workaround, which is to use RMI-IIOP objects extending PortableRemoteObject as bridges between CORBA and classic RMI servers.
The EJB specification, at one time was not consistent WRT how one should convert the result of a JNDI
lookup to an EJB interface instance and such issues. As a result you will see examples showing direct casting, and some
examples show using the function: javax.rmi.PortableRemoteObject.narrow.
Furthermore, the spec. said that some implementations will require the narrow method, and some will not. It is considered mandatory for implementations based on IIOP.
So what is actually going on? CORBA based implementations, in general, do not return the "actual" server type from a method invocation. They return the "signature" type which can then be narrowed to a more specific type, if need be. In certain circumstances, determining if an object can be narrowed to the desired type requires an RPC to the server implementing the object. However, although our implementation is built on CORBA, we thought it was more user friendly to support the casting behavior, when possible. This was how our beta Container behaved. However, as stated above, sometimes this behavior requires an RPC to the server.
So, now we can start to answer the question.
Using javax.rmi.PortableRemoteObject.narrow will succeed in all cases, and is preferred. In fact the final EJB 1.1 spec pretty much mandates the use of PortableRemoteObject always.
The javax.rmi.PortableRemoteObject.narrow method takes two parameters: the instance to narrow, and the class to narrow to. So, in the examples that depend on the cast, you would change the following
from:
AccountHome savingsHome = (AccountHome) context.lookup("accounts/savings");
AccountHome checkingHome = (AccountHome) context.lookup("accounts/checking");
to:
AccountHome savingsHome = (AccountHome) javax.rmi.PortableRemoteObject.narrow
(context.lookup("accounts/savings"), AccountHome.class);
AccountHome checkingHome = (AccountHome) javax.rmi.PortableRemoteObject.narrow
(context.lookup("accounts/checking"), AccountHome.class);
On a related note …
You should never have to use 1) passing an Account object back to the client when the signature of the method used is - "Account getAccount1()"
2) passing back the same object with the method signature - "Object getAccount2()"
3) using a Java object (say Wallet) to store the Account object in three ways (an Account, an EJBObject, and an Object) and passing this structure back to the client.
The client will print out the xxx.getClass().getName() for all the Account objects returned to the client. The output at the client looks like this:
"vbj Client
Class name for acnt1 (Account): _Account_Stub
Class name for acnt2 (Object): com.inprise.vbroker.orb.ObjectImpl
Wallet - Account: _Account_Stub
Wallet - EJBObject: javax.ejb._EJBObject_Stub
Wallet - Object: com.inprise.vbroker.orb.ObjectImpl
This cast ((Account)aw.acnt2) blows up:
Exception in thread "main" java.lang.ClassCastException:
javax.ejb._EJBObject_Stub
at BankClient.main(BankClient.java:32)
"
Strangely, the result is not as expected (the same "Account" type for all cases).
javax.rmi.PortableRemoteObject.narrow(object, Account.class)
This will return you an instance of Account, as you expect.
This example provides an excellent motivation for forcing users to use the PRO.narrow.
So no, the Helper class is still very much required.
Attempting to return unmarshalable class: class
java.util.VectorEnumerator
java.io.NotSerializableException: java.util.VectorEnumerator
That is understandable as VectorEnumerator isn't serializable and therefore it can't be
returned to the client. Makes sense.
But when I do _exactly_ the same thing in ejbFind<>(), everything works fine!
1) The Enumeration returned by java.util.Vector.elements() is not Serializable, and thus cannot be returned from a remote interface method (e.g. EJB business method)
2) You can return Vector.elements() from a finder method.
For (1), you can mimic the cart example, where a VectorEnumeration class is created to return a Serializable Enumeration computed from a Vector. This is the only correct work-around.
So then, why does (2) work? If you look closely, the Enumeration returned by a bean's implementation of a finder method contains primary keys as the elements. However, the Enumeration returned by the home's finder method contains EJBObject elements. Thus, the container is computing a new Enumeration (containing EJBObjects) based on the old Enumeration (containing primary keys). While the container does this, it also constructs a different Enumeration which happens to be Serializable.
public Class _class;
...
// and in constructor,
_class = acnt.getClass();
So the _class object will be serialized to the client when AccountWallet is sent over. Now, running the
client, I have two kind of errors:
1) a NullPointerException when at the client I do this:
AccountWallet aw = teller.getAccountWallet();
aw._class.toString();
2) the Client crashes with an Application Error, when at the client I do this:
aw._class.getName().toString();
The trick that we use, in our implementation of javax.ejb.EJBMetaData, for example, which "appears" to pass classes, is to have two fields in our objects:
private transient Class _homeInterfaceClass;
private String _homeInterfaceClassName;
Then, the getHomeInterfaceClass method would be:
public Class getHomeInterfaceClass() {
if(_homeInterfaceClass == null) {
_homeInterfaceClass = Class.forName(_homeInterfaceClassName);
}
return _homeInterfaceClass;
}
So, what is actually serialized is a string representing the class name. Again, the Class instance itself cannot
be marshaled in our current product. We will look to support this data type in a subsequent release.
You cannot have certain RMI definitions like a package named employee within which is an interface named Employee with only a case difference. This prevents java2iiop from mapping these classes into IDL. This is unfortunately an explicit restriction in the reverse mapping. IDL is case insensitive (due to a requirement to support target languages which are case insensitive). However, the Java-to-IDL reverse mapping does attempt to handle Java's case sensitivity. For example, the reverse mapping supports method names which differ only by case. However, the reverse mapping does not support "type" names which differ only by case, and peculiarly, package names map to an IDL construct (module) which is considered a type.
Note that all of this is as per the OMG specifications; these are not limitations in our product, per se. As such, these same limitations will exist in any compliant EJB product built using RMI-over-IIOP. Change either your package name or your interface name to not hit this restriction.
Here's a partial dump of OSFind running on my system: > > REPOSITORY ID: IDL:omg.org/CosTransactions/TransactionFactory:1.0 > OBJECT NAME: EJB/JTS[test] > OBJECT NAME: EJB/JTS[test] > > REPOSITORY ID: RMI:us.oh.state.dot.pas.ProjectHome:0000000000000000 > OBJECT NAME: project > OBJECT NAME: odot/pas/project > > Why the RMI tag on the object that I deployed? Is RMI being used as the > protocol anywhere in this process?The RMI repository ID is used to identify remote interfaces that are not "CORBA"(i.e. did not come from IDL) and is specified by the java to IDL reverse mapping in CORBA 2.3. We use IIOP as the communication protocol, and no RMI (protocol) anywhere.
| If the Original is | It is mapped to |
| java.lang.Error | java.rmi.ServerError |
| java.rmi.RemoteException | java.rmi.ServerException |
| java.lang.RuntimeExcepition | java.lang.RuntimeException |