New York
University
Computer
Science Department
Courant
Institute of Mathematical Sciences
Objects
by Value over CORBA
Course Title: Application Servers Course
Number: g22.3033-011
Instructor: Jean-Claude Franchitti Session: 4
The Object by Value Specification
The specification adds new keywords to the IDL
syntax and new constructs to the grammar. The additional keywords are:
·
valuetype
·
ValueBase
·
truncatable
·
custom
·
public
·
init
·
abstract
·
supports
The valuetype
is a new IDL construct that is a cross between the existing struct and an interface. Sensibly, the specification doesn’t change the rules for
marshalling these existing types. The struct
is always passed by value and the interface is always passed by reference. The valuetype itself is passed by value,
like the struct, but can contain
operation declarations, like the interface.
But a valuetype doesn’t have to
inherit from CORBA::Object.
Here is an example:
module
Test { valuetype
OBV { private
long amount; long
getAmount(); init(in
long newAmount); };
OBV
getOBV(); }; }; |
OBV is declared as a valuetype. It contains both member variables and member operations.
Note that the long amount is not
like an attribute declaration in an interface. It defines state data and maps
to a member variable. The member variables in the valuetype can be private
or public access. The operations are
always public. So the above
declaration is functionally equivalent to:
valuetype
OBV { public
long amount; init(in
long newAmount); }; |
The second operation init declares an operation through which an instance of the valuetype can be created. The mapping
of the initializer depends on the target language. In C++ and Java the init
operations are mapped to constructors.
This is the generated code for the valuetype OBV produced by the rmi-iiop implementation from IBM/Sun.
package
Test; public class OBV
implements org.omg.CORBA.portable.ValueBase { int
amount = (int)0; public
OBV (int newAmount) { } protected
OBV () {} public
String[] _truncatable_ids() { return
Test.OBVHelper.get_instance().get_truncatable_base_ids(); } public
int getAmount () { } } // class OBV |
Note that amount maps to a member variable, the init operation maps to a constructor
and each of the operations maps to any empty method. To complete the
implementation the method bodies must be implemented.
Note that the rmi-iiop implementation doesn’t
conform exactly to the specification. The implements
org.omg.CORBA.portable.ValueBase isn’t correct. ValueBase is the base type for all value types. Just as CORBA::Object is the base type for all
interface types. The mapping for ValueBase in the revised specification is implements java.io.serializable.
In this simple example the additional code to complete
the implementation is trivial, thus:
package
Test; public class OBV
implements org.omg.CORBA.portable.ValueBase { int
amount = (int)0; public
OBV (int newAmount) { this.amount
= newAmount; } protected
OBV () {} public
String[] _truncatable_ids() { return
Test.OBVHelper.get_instance().get_truncatable_base_ids(); } public
int getAmount () { return
amount; } } // class OBV |
The interface ByValue
from the above IDL can be implemented in the normal way.
package
Test; import org.omg.CORBA.ORB; public class
ByValueImpl extends _ByValueImplBase { public
Test.OBV getOBV () { return
new OBV(10); } } |
When getOBV()
is called by a client, apparently a copy of the object implementing the OBV valuetype will be transmitted to the calling client. If the client
then calls getAmount() it will be on
its own local copy.
Additional Semantic Meaning
The valuetype
carries with it additional semantic meaning. It can be used to define an
arbitrary graph of related objects including recursion and cycles. The
referential integrity of the objects will be maintained during transmission and
the target process will have an identical copy of the source object graph. It
is also admissible to transmit null references in the graph. This copy of the valuetypes includes maintaining sharing
of instances across multiple parameters passed in a remote call.
For example, an operation may be declared with two
parameters such that the operation should pass two copies of the same string.
The standard IDL type string does not support sharing. This valuetype does:
valuetype
SharedString { public
string theString; }; |
This could then be used in a call thus:
void
sendTheStrings (in SharedString string1, in SharedString string2);
If a client calls this method passing the same
instance of SharedString for the two
parameters, then the server will receive a single copy of the parameter
referenced from both string1 and string2. If the IDL string type had
been used instead, then the server would receive two separate copies of the
string. This is a useful construct but the IDL is long winded. A functionally
equivalent value box declaration can be made which simplifies the IDL shown
above. SharedString could have been
declared as:
valuetype SharedString string;
This is a value box declaration. Two useful value
box declarations are made in the CORBA module for the string and wstring
types: -
module
CORBA { valuetype
StringValue string; valuetype
WstringValue wstring; }; |
These simple examples serve to illustrate the major
difference between the new valuetype
and existing IDL types. For the first time it is possible to define an object
type in IDL that has both state and behavior that can be passed by value to
another process. This capability has always been available in RMI, which is
architecturally very similar to CORBA. Why has it been possible to pass objects
by value using RMI but not CORBA?
CORBA and RMI
Both CORBA and RMI provide mechanisms for exposing
objects to remote invocations from across the network. CORBA is language
independent but RMI is Java only. Both mechanisms are similar in
implementation. Each requires the definition of a remote interface. RMI uses a
Java interface that must inherit from java.rmi.Remote.
CORBA requires an interface to be defined in OMG IDL. In both cases a class is
created which implements the remote interface. However, since IDL is language
independent a pre-processor is run before implementing the interface to
generate language specific code to support the distribution. Since RMI
interfaces are defined in Java, a post-processor (rmic) is used to generate the
supporting distribution classes after implementation. Both mechanisms result in
an object implementation that is always passed by remote reference.
Other CORBA parameters, those that are not of type interface are always passed by value.
These other types are defined in IDL but only the interface type can have operations. The struct, for example, can have state but not operations.
RMI requires that parameters that are to be passed
through a remote interface method are serializable objects. So it is possible
to pass objects by value from one virtual machine to another if they don’t
implement a remote interface but are serializable. But serialization only
passes the state of the object, not its implementation. RMI relies on the
ability of a virtual machine to dynamically load class bytes at runtime. When
an object is being de-serialized within another virtual machine the class bytes
can, if necessary, be loaded from the classpath (or codebase if it is an
applet).
RMI is a Java only solution to distribution that
takes advantage of the dynamic class loading to pass the implementation of an
object (the class bytes) to another process. Since CORBA is language
independent such a mechanism cannot be used.
So how does CORBA Object by Value handle passing the
state and implementation of an object from one process to another? Like Java
serialization it doesn’t pass the implementation, just the state. For many
languages, such as C++, a compatible implementation of the valuetype must already be available in the process. If not a NO_IMPLEMENT exception will be thrown
indicating that there is no local implementation of the type and the parameter
cannot be unmarshalled. (If the source and target languages are Java then the
dynamic class loading can be used to pass the class implementation. A system
context can be passed with the parameters in the request or reply within which
a URL to a codebase can be defined.)
So the valuetype
definition, whilst it can include operations, only guarantees the transmission
of the state data. It is the responsibility of the target process to
instantiate a compatible implementation of the valuetype and initialise it using the transmitted values.
Why is OBV Important for CORBA and RMI?
The RMI distribution mechanism currently uses its
own protocol RMP to transmit the message. As soon as Sun released an FCS (first
customer shipment) of RMI over IIOP, the CORBA protocol, the OMG ratified a Java to IDL mapping.
The Java to IDL mapping defines the mapping from an RMI remote interface written in Java to OMG IDL. So given an RMI remote interface, an IDL equivalent can be generated. This IDL could then be passed through an IDL to Java pre-compiler to generate the Java stub and skeleton! However, it is more likely that the IDL would be used to generate code other than Java, say C++. This would allow a C++ client to invoke on an RMI server implementation.
This is significant for EJB (Enterprise Java Beans)
in particular since it uses Java RMI remote interfaces for the enterprise
beans. EJB servers implemented over CORBA could use RMI over IIOP directly or
auto-generate the IDL from the Java remote interface together with a wrapper
CORBA implementation to route incoming CORBA requests to the bean.
The Java to IDL mapping makes extensive use of the valuetype to map RMI parameters to
CORBA IDL. So the CORBA implementation will be required to support them. Here
is a simple example of an RMI remote interface:
package
Finance; import java.rmi.*; public interface
Account extends Remote { void
credit(double amount) throws RemoteException; void
debit(double amount) throws RemoteException; double
getBalance() throws RemoteException; String
getName() throws RemoteException; } |
The interface methods use common ‘built-in’
parameter types such as double and String. String is of course a class. Using
the rmic compiler from the rmi-iiop kit the reverse mapping to IDL can be
generated. After compiling the Account.java file running rmic –idl Finance.Account results in a file Account.idl. The
generated IDL is:
#include
"orb.idl" #ifndef
__Finance_Account__ #define
__Finance_Account__ module Finance { interface
Account { void
credit(in double arg0 ); void
debit(in double arg0 ); readonly
attribute double balance; readonly
attribute ::CORBA::WStringValue name; }; #pragma
ID Account "RMI:Finance.Account:0000000000000000" }; #endif |
Note the mapping of the String type. It is
transformed into a ::CORBA::WStringValue
type. This is the boxed value type pre-defined for a CORBA string. So even with
the simplest of Java interfaces, the valuetype
is used in the reverse mapping to IDL. Any Java type that implements java.io.Serializable will be mapped to
a valuetype.
Other Features of Object by Value
The Object by Value specification includes several
other features. The examples so far have not used the keywords supports, truncatable, custom or abstract.
Supporting Interfaces
A valuetype
does not have to declare all of its own operations; it can support an interface
instead.
interface
IName { string
getname(); void
setName(in string theName); }; valuetype NameObject
supports Iname { private
string myName; }; |
The generated code for the IDL above is:
public
class NameObject implements org.omg.CORBA.portable.ValueBase,
InameOperations { String
myName = null; protected
NameObject () {} public
String[] _truncatable_ids() { return
NameObjectHelper.get_instance().get_truncatable_base_ids(); } public
String getname () { } public
void setName (String theName) { } } // class NameObject |
This class implementation can be used as the target
for a TIE implementation of the IName
interface. Currently most of the ORB implementations that support TIE do so in
a vendor specific way. The portable object adaptor mapping for Java specifies a
standard TIE implementation class <interface
Name>_TIE. The example IDL above would generate a class IName_TIE. This class holds a reference
to a member of type INameOperations.
Note that the generated class implements this interface and is thus a suitable
delegate object for the IName_TIE
class. This means that the NameObject
valuetype implementation can be used as a parameter of type NameObject or as the target for a TIE
of type IName. Note that the
semantics of the interface and valutype
are not changed. If a reference to the NameObject
is passed through an IDL operation then the client receives a local copy. If
the NameObject is passed via a TIE
then the client makes remote invocations on the IName stub back to the server side copy.
Figure 1 illustrates this behavior.
Figure 1: A valuetype can support an interface. The client can receive a local copy of the valuteype or a remote interface
Inheritance and the Valuetype
A valuetype
can inherit from another valuetype.
valuetype
NameObject { private
string myName; }; |
Valuetype
NameAddressObject : NameObject { Private
string myAddress; }; |
The mapping to Java presents no surprises:
public
class NameAddressObject extends NameObject { String
myAddress = null; protected
NameAddressObject () {} public
String[] _truncatable_ids() { return
NameAddressObjectHelper.get_instance().get_truncatable_base_ids(); } } // class
NameAddressObject |
The derived valuetype
simply extends the base type. But this could lead to a situation in which the
client could instantiate the base type but does not have a valid implementation
of the derived valuetype. Should the
ORB be allowed to truncate the value to its base type only? In this example
should the ORB create a NameObject
rather than a NameAddessObject? The truncatable keyword can be used to
specify that it is safe to truncate to the base type, thus:
<snip>
valuetype NameAddressObject : truncatable NameObject {
<snip>
A valuetype
can only inherit from one other stateful valuetype.
However, a valuetype can be declared
abstract. An abstract valuetype can
have no state, only operations. But a valuetype can multiply inherit from abstract
valuetypes. The table below summarises the permissible inheritance for the
valuetype.
may
inherit from: |
Interface |
Abstract Value |
Stateful Value |
Boxed value |
Interface |
Multiple |
no |
no |
no |
Abstract Value |
Supports |
multiple |
no |
no |
Stateful Value |
Supports single |
multiple |
single (may be truncatable) |
no |
Boxed Value |
No |
no |
no |
no |
If there is inheritance from both a stateful
valuetype and abstract valutypes, then the stateful valuetype must be first in
the list (preceeded by truncatable
if appropriate).
module
Multiples { abstract
valuetype MyAbstractBase { void
doit(); };
void
doit2(); };
private
string name; };
private
string nameagain; }; }; |
Abstract valuetypes map to an interface, not a
class. So the MyAbstractBase type
maps thus:
public
interface MyAbstractBase extends org.omg.CORBA.portable.ValueBase { void
doit (); } // interface
MyAbstractBase |
So the mapping for MyValue is a single extends
from MyStateFullbase and multiple implements from the abstract valuetypes
and the interface.
public
class MyValue extends Multiples.MyStateFullBase implements Multiples.MyAbstractBase, Multiples.MyAbstractBase2,
Multiples.AnInterfaceOperations { <snip> |
Custom Marshalling
The custom keyword can be used to define a valuetype that will be marshalled using user-defined routines rather than the standard IDL marshalling. Each language mapping provides an entry point for the custom marshalling.
custom
valuetype Customer { //optional
state }; |
The custom types aren’t intended to be a regularly
used feature. They are for integration with legacy libraries and systems to
permit passing of existing types that can’t be defined as a ‘standard’
valuetype.
Abstract Interfaces
The abstract interface adds a final twist to the Object by Value extensions. An abstract interface does not have to inherit from CORBA::Object. It can be implemented as a valuetype or as a ‘normal’ remote object. The abstract keyword is used:
abstract
interface ABS { long
getAmount(); };
string
getMe(); };
private
long amount; init(in
long newAmount); }; interface ByValue { OBV
getOBV(); ABS
getABS(); }; |
Note that interface Concrete inherits from the abstract interface ABS. The valuetype OBV
supports the ABS interface. An
interface operation that uses ABS as
a parameter type (such as getABS()
above) could pass either an instance of OBV
or an instance of an object that implements Concrete. What the client receives depends on the type of the
actual parameter that is passed. If an OBV
is passed the client gets a local copy. If an implementation of Concrete is passed then the client gets
a proxy reference to the server side implementation object. Figure 2
illustrates this behavior.
Figure 2: An
abstract interface can be passed by value or by remote reference. The client
can receive a local copy of a valuetype or a remote reference via the same
operation call
The mapping for ‘supports interface’ is subtly
different to ‘supports abstract interface’. Since ABS is abstract the mapped OBV
class implements ABS rather than ABSOperations. So when an
implementation of OBV is passed to the operation it can be done so directly,
rather than via a TIE object which is the case when the interface is not
abstract.
public class OBV implements org.omg.CORBA.portable.ValueBase, Test.ABS {
<snip>
This behavior is similar to that described above for a valuetype supporting an interface. The big difference is that the first example had two methods, one that returned the valuetype and one that returned the interface type. The client knows whether a local copy or a remote reference is going to be returned. With the abstract interface there is only one operation that returns an ABS. The client cannot know in advance if a local copy or a remote reference will be returned.
Summary
The Object by Value specification adds significantly
to the IDL syntax and semantics. The ability to define an object that can be
passed by value rather than reference opens up new possibilities for interface
design. Since an object graph can be passed that maintains its referential integrity
a new type of ‘CORBA Object’ can be used. The traditional approach of hiding
the implementation behind a remote interface can be replaced with a more
dynamic approach wherein ‘portable business objects’ can be passed to the
client process. The local objects can then do work for the client perhaps being
passed back to a server with updated or additional information.
The problem still remains that most languages don’t
support the dynamic loading of class implementations at runtime. So C++
implementations must already have a compatible implementation of the valuetype.
It is generally accepted that the Java ORB implementations will be the first to implement the specification. It remains to be seen how quickly the C++ and other ORB implementations will add the Object by Value extensions.