This note discusses the details of how we can introduce a versioning mechanism into the Java ORB for the purpose of fixing our current interoperability problems. The basic concept is that all ORBs that we have not yet shipped will include versioning information in every request, response, and IOR. The rest of this note discusses the exact format of the versioning information and how it can be interpreted to achieve interoperability.
I have thought of a number of possibilities for the format of version information:
Note that for a version in the range 0-127, this is exactly the same as option 1.
As I stated previously, the reason I am thinking about the format of the version ID is if we need to know something like JDK version or OS in the version. Perhaps we should allow for this, but it's hard to decide how far to go. For example, we could represent a version by a string (or an equivalent octet sequence in option 5) in the following format:
<major ORB version>:<minor ORB version>:<(java.version)>:<(os.name)>:<(os.arch)>:<(os.version>)
where (name) means the result of System.getProperty(name).
Given all of these considerations, I think we should use option 5 for the version ID. All of our ORBs will currently restrict the version to a single octet in the 0-127 range. The location of this data is discussed in "Object Key Formats".
We will use exactly the same form for the version in both the object key and in a new service context. The new service context ID should be allocated in the range 0x4e454f00-0x4e454f0f (see ptc/00-07-06.txt). So, let's just define another constant TAG_ORB_VERSION = 0x4e454f00 in ORBConstants for this service context tag. This service context should be present in all GIOP request and reply messages sent by NEW and NEWER ORBs. Its data will consist solely of a series of octets (no length is needed) encoded as above.
A service context is then encoded using a CDR encapsulation as follows:
ID (4 bytes) |
Length (4 bytes) |
endianness (1 byte) |
1xxx |
1xxx |
... |
0xxx |
This is easy to read as follows:
If we attempt to read past the end of the encapsulation, we get a marshaling error.
For the following discussion, we only need to distinguish the following cases based on the object version:
ORB Type |
Definition |
Encoding |
Magic |
FOREIGN | Foreign ORB |
none |
N/A |
OLD | Old Sun ORB (JDK 1.2, RMI-IIOP Standard Extension, JDK 1.3, J2EE 1.0, 1.1, 1.2) |
1 |
0xAFABCAFE |
NEW | JDK 1.3.1 ORB |
2 |
0xAFABCAFF |
NEWER | JDK 1.4 or later ORB, including J2EE 1.3 or later. |
3 |
0xAFABCB00 |
When the version in found in the object key, it defines the version of the ORB that created the IOR. When the version is found in a service context, it defines the version of the ORB that sent the GIOP request or reply. Note that the NEW and NEWER ORBs need different MAGIC values, since the object key structure is actually different in these cases. Also, only NEWER ORBs actually include a version in the object key: the version in the object key is implicit in the MAGIC value for OLD and NEW ORBs.
The current object key formats are as follows:
MAGIC = 0xAFABCAFE
Object Key Type |
Field Name |
Field Type |
JIDL |
MAGIC | long |
SCID | long | |
ID | sequence<octet> | |
POA |
MAGIC | long |
SCID | long | |
SERVERID | long | |
ORBID | long | |
POAID | long | |
ID | sequence<octet> |
Proposed new formats for NEW (JDK 1.3.1) ORB. No versions in the object key because this version has a unique magic number:
MAGIC = 0xAFABCAFF
Object Key Type |
Field Name |
Field Type |
JIDL |
MAGIC | long |
SCID | long | |
ID | sequence<octet> | |
POA |
MAGIC | long |
SCID | long | |
SERVERID | long | |
ORBID | long | |
POAID | long | |
ID | sequence<octet> |
New formats for NEWER (JDK 1.4 and later) ORBs:
MAGIC = 0xAFABCB00
Object Key Type |
Field Name |
Field Type |
JIDL |
MAGIC | long |
SCID | long | |
ID | sequence<octet> | |
VERSION | n octets | |
POA |
MAGIC | long |
SCID | long | |
SERVERID | long | |
ORBID | string | |
POANAME | sequence<string> | |
ID | sequence<octet> | |
VERSION | n octets |
Note that all of these are marshaled according to CDR rules. Also, note that the previous discussion here about MAGIC was wrong: we simply MUST change the MAGIC every time any part of the object key structure changes (or else we need to introduce a new subcontract, but that is actually a larger change, and I do not want to have multiple versions of the same subcontract that differ only in trivial encoding details.)
I don't want to expose the exact encoding of versions to all of the code that needs to check the version. Instead, I would like to define an ORBVersion interface as follows:
public interface ORBVersion {
int FOREIGN = 0 ;
int OLD = 1 ;
int NEW = 2 ;
int NEWER = 3 ;
/** Return the ORB version type.
* Always returns one of the constants defined in this interface
*/
int getORBType() ;
/** Write the version to os as a series of properly encoded octets.
void write( OutputStream os ) ;
}
This interface provides all of the version information that we currently need. It can be extended as needed in the future.
We also need a factory class:
public class ORBVersionFactory {
private ORBVersionFactory() {}
/** Return the ORB version that this ORB is using.
*/
public static ORBVersion getORBVersion()
{ ... }
/** Get the ORB version encoded in the input stream.
*/
public static ORBVersion create( InputStream is )
{ ... }
}
The factory can be efficiently implemented rather easily by pre-allocating 4 instances of an implementation of ORBVersion. Since the ORBVersion does not need to be mutable, we can just share the pre-allocated instances as needed. Both of these ORBVersion classes should be identical in Ladybird and Merlin.
The ORB version service context also needs to be supported. To do this, we simply use the ORBVersionFactory to read the data when the service context is constructed. This simply requires adding a new ORBVersionServiceContext class to handle the service context.
Getting the ORB version from a GIOP request proceeds something like the following:
if (version service context is present)
get version from service context
else if (request is SUN-1.0 protocol(see interoperability discussion))
version is OLD
else
version is FOREIGN
For getting the version from the IOR, we want something like:
ior.getProfile().getVersion()
so that the code using the version is independent of the different representations of IORs in Ladybird and Merlin. The details of how this is implemented are substantially different in the two code bases.
In Ladybird, we will probably implement the versioning by going directly to the object key. We can do this as follows:
I think we will just code this directly in core.Profile in Ladybird and not worry about the fact that too much of the code knows all about the object key formats. That problem is fixed in Merlin.
Implementing IOR versioning in Merlin is pretty simple:
POAObjectKeyTemplate, JIDLObjectKeyTemplate, and WireObjectKeyTemplate are the three concrete classes that represent all possible object key templates in our ORB. They should be entirely unaffected by the version related changes.
We need a way to decide upon receiving a GIOP request what kind of ORB sent it. Since both NEW and NEWER ORBs will always send a Sun protocol version message, those cases are easy. Distinguishing FOREIGN and OLD is more difficult. The old (and non-standard) GIOP protocol always sent GIOP 1.0 requests with CodeSet and CodeBase components. Since these two components are not defined for GIOP 1.0, it is unlikely that a FOREIGN ORB would send such a request. Therefore, we will assume that any such message is from an OLD or NEW ORB, and NEW can be distinguished based on the version. I will refer to the OLD and NEW ORB protocol as SUN-1.0.
Then we can make everything interoperate across all versions as follows:
Invoking ORB |
ORB of IOR |
Receiving ORB |
Detection Mechanisms |
Protocol |
Serialization |
FOREIGN | FOREIGN | FOREIGN |
DON'T' CARE |
||
FOREIGN | OLD | OLD |
DON'T CARE (probably does not interoperate) |
||
FOREIGN | NEW | NEW | Server: GIOP request is not SUN-1.0 |
compliant |
compliant |
FOREIGN | NEW | NEWER | |||
FOREIGN | NEWER | NEWER | |||
OLD | FOREIGN | FOREIGN |
DON'T CARE (probably does not interoperate) |
||
OLD | OLD | OLD |
currently interoperable |
||
OLD | NEW | NEW | Server: GIOP request is SUN-1.0 |
SUN-1.0 |
old |
OLD | NEW | NEWER | |||
OLD | NEWER | NEWER | |||
NEW | FOREIGN | FOREIGN | Client: IOR does not contain magic |
SUN-1.0 |
compliant |
NEW | OLD | OLD | Client: IOR contains old magic |
old |
|
NEW | NEW | NEW | Client: IOR contains new magic, NEW version |
compliant |
|
NEW | NEW | NEWER | Client: IOR contains new magic, NEW version Server: receives target with NEW version |
||
NEW | NEWER | NEWER | Client: IOR contains new magic Server: receives target with NEWER version |
||
NEWER | FOREIGN | FOREIGN | Client: IOR does not contain magic |
compliant |
compliant |
NEWER | OLD | OLD | Client: IOR contains old magic |
SUN-1.0 |
old |
NEWER | NEW | NEW | Client: IOR contains new magic, NEW version |
SUN-1.0 |
compliant |
NEWER | NEW | NEWER | Client: IOR contains new magic, NEW version Server: receives target with NEW version |
SUN-1.0 |
|
NEWER | NEWER | NEWER | Client: IOR contains new magic, NEWER version Server: receives target with NEWER version |
compliant |
Note that the versioning is complicated by the fact that an ORB supporting a POA may receive requests on persistent object references that were created by an ORB of an earlier version that supported the POA. We can detect this by checking the version of the target that was received in the GIOP request, which may be different from the version of the receiving ORB. We only support backwards compatibility here: it is not always possible to run a persistent server for an object reference on an ORB whose version is earlier than the version in the object reference. In particular, a persistent IOR created by a NEWER ORB cannot be served by a NEW ORB. The last two rows in the table are significant: the protocol accepted by the server is different in these two cases because the client can only chose the version based on the IOR, not on the actual version of the server.