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);

};


interface ByValue {

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();

};


abstract valuetype MyAbstractBase2 {

void doit2();

};


valuetype MyStateFullBase {

private string name;

};


valuetype MyValue : truncatable MyStateFullBase, MyAbstractBase, MyAbstractBase2 {

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();

};


interface Concrete : ABS {

string getMe();

};


valuetype OBV supports ABS {

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.