ORB NotesKen Cavanaugh5/12/06 |
Abstract: Comprehensive documentation on the Sun Java ORB.Contents
- Chapter 1 Starting Points
- Chapter 2 The ORB Class
- Chapter 3 Dispatch Path Overview
- Chapter 4 Presentation
- Chapter 5 Encoding
- Chapter 6 Protocol
- Chapter 7 Transport
- Chapter 8 Other Aspects of the ORB
- Chapter 9 Utilities
- Chapter 10 Living with our legacy
- Chapter 11 Compilers
- Chapter 12 Future Directions
This document originally described CORAB development internally in Sun’s TeamWare environment. We have since switched to using Mercurial, both internally and externally. The glassfish-corba project still uses CVS, but only to maintain easy access to the documents.
Some of the policies and procedures described here will be changed as we continue moving all of our work to the open internet glassfish-corba project.
Over the years, we have established a fairly successful development methodology for working on the ORB. Any piece of work from a minor bugfix to a major new subsystem iterates over the same stages one or more times. I’ll discuss this briefly here, but the details will appear below.
TBD: Currently we conduct all code reviews in real time, usually via phone conference (since this has been a distributed team for a long time). We need to investigate this in the open source world, and find a more scalable method that works asynchronously. Something like Google’s Mondrian would be a good choice, if it were available.
The workspace is divided into a number of directories. The current structure will change somewhat after I finish work on another putback that restructures the workspace to simplify its organization and remove obsolete junk.
Directory | Purpose |
build | created during build process |
build/classes | compiled classes |
build/gensrc | Java source code generated by build |
build/lib | miscellaneous idl files for the interface repository |
| build/release/lib | Jar files that are the build results:
|
build/rename/ee | Where the ant rename target puts a copy of the workspace |
lib | various jar files needed to build and test the ORB:
|
make | contains ant build files |
nbproject | contains NetBeans support to treat workspace as a NetBeans project |
orblib | Standalone ORB library source files (delivered to orblib.jar) |
src | main ORB source code (delivered to peorb.jar) |
test | ORB test source (and orblibrary as well at present) |
tools | IOR parser |
There are quite a few files here. This will eventually include all of the useful files from /java/j2ee/CORBA. Most of the doc files in the current workspace are useless, with one important exception: the eea1 directory. These are Everett Anderson’s notes on the design and implementation of the GIOP layer, and are still quite useful. This also documents the RMI-IIOP stream format version 2 implementation.
Generally, most ORB implementation source packages have two parts: an interface defined in an spi package, and an implementation in an impl package. We will generally discuss these together.
The ORB source code is divided into several parts:
Most of the ORB is packaged under the com.sun.corba.ee package. A version of the ORB code exists in this package (or in a slightly different version) in every JDK since 1.2. We also deliver the ORB code to the Sun application server (now project GlassFish). To avoid possibly collisions between the classes in the JDK and the classes in GlassFish, we rename all the files in the ORB to the com.sun.corba.ee package.
This rename is done automatically using a Java program (com.sun.corba.ee.spi.orbutil.file.WorkspaceRename) that is part of the ORB library. There is an ant target (rename) for this as well. All that is needed for the rename is:
(TBD: make rename do a fast check for the orblib.jar, and build it if it is not present. Right now, calling ant orb-library takes around 5 seconds, which is too long, since the incremental rename is <1 second). The ant rename target on a fast local file system should take around 30 seconds or so (much faster than the old scripted version).
Of course, the rename also interferes with the standard edit-compile-test-debug cycle. I have a build environment built around vim that gets around this problem, but it is difficult to impossible to deal with renaming with any IDE that I know of.
The rename is no longer necessary for development in GlassFish-CORBA. Here all that is required is an ant build, from NetBeans, standalone, or from any other tool a developer might care to use. The only time a rename is required is when the jar files are created that are delivered to GlassFish for integration in the app server.
The ORB can only be built with ant. All makefile support has been removed.
The ORB can be build either standalone (the mode used to deliver the ORB into GlassFish), or as part of the JDK. Here we just focus on the app server specific build. You MUST use JDK 5 or later.
Assume <ws> is the ORB workspace. The basic build sequence is:
Assuming a local file system is used on a fast machine, the rename should take around 30 seconds, and the build 60 seconds or less. The test target builds the tests (build-tests) which takes around 60-90 seconds, and then runs all of the tests, which should take around 40 minutes.
I generally use a script to automate this. It is also possible to do a build from NetBeans. It used to be necessary to rename in order to build and run the tests, but that has been fixed in the GlassFish-CORBA project.
Another small note: it is possible to generate JavaDocs for the CORBA SPI. To do this, simply go to the make directory in the renamed version of the workspace, and run “ant javadoc”. The resulting JavaDocs may then be accessed from <ws>/build/rename/ee/build/release/docs/index.html (exercise for the reader: fix all of the JavaDoc warnings that show up). It should also be possible to create the SPI javadocs without renaming, and the NetBean project supports this as well.
Here is a more detailed description of the ant targets that are useful to a developer:
Target Name | Function |
build | builds the ORB library and main ORB code (but not the tests) |
build-tests | Runs idlj and rmic on test files, and calls compile-tests |
compile-tests | Only compiles the tests (useful if you are changing a test, as build-tests
takes a lot longer to run |
orb-library | Just build the ORB library (needed for rename) |
update-copyright-headers | Update all copyright headers in the workspace to the contents of make/copyright-information/copyright.txt |
validate-copyright-headers | Check that the copyright headers match copyright.txt |
clean | Removes all generated files, renamed workspace, and test results |
clean-emma | Removes emma results |
clean-tests | Removes test results |
javadoc | generate javadocs for ORB SPI (TBD: need to handle ORB library as
well) |
findbugs | generate a findbugs report on the ORB |
emma | Runs clean-emma, emma-instr, test, emma-report |
emma-instr | Instrument all of the class files so that emma can generate a coverage
report |
emma-report | Generate an emma report (found in <ws>/build/coverage/coverage.html) |
There are also a number of targets useful for running tests, which are discussed in 1.4.
Let’s look at the ant files first. There are several .xml files, all included in build.xml:
There are a number of other files as well:
The ORB has many tests in several different test suites (docs/TestCases.sxc gives some details of the contents and the numbers of test cases in the test suites, from a rough manual count). The test suites are:
There are a number of libraries in the lib directory. These are all used either for building or testing.
The ORB developer tests are found in the directories test and optional/test. The makefile for running the tests is in test/make/Makefile, but the test suites can also be run from make/Makefile.corba. This table gives a brief overview of the available test suites:
| Suite Name | Test File | ant target in GlassFish-CORBA | Purpose |
| all | - | test-all | runs all test suites |
| IBM | test/AllTests.desc | test-rmi-iiop | Old IBM tests |
| corba | corba/CORBATests.tdesc | test-corba | Most of the newer tests |
| pi | pi/PITests.tdesc | test-pi | Portable Interceptors tests |
| copyobject | corba/CopyObjectTests.tdesc | test-copyobject | copyobject test |
| naming | naming/NamingTests.tdesc | test-naming | naming tests |
| hopper | hopper/HopperTests.tdesc | test-hopper | Bugfixes for JDK 1.4.1 |
| mantis | mantis/MantisTests.tdesc | test-mantis | Bugfixes for JDK 1.4.2 |
| simpleperf | performance/Tests.tdesc | test-perf | co-located call performance test |
All of the tests are run from <ws>/make using the appropriate targets. The output of the tests (stdout and stderr) are redirected to log files. These log files are located in <ws>/test/make/gen, under the package name of the individual tests in the test suite. Most of the tests of interest are in the corba test suite, so making those pass first is usually all that is needed (the others tend to pass too, once the CORBA tests pass). This is not always the case, particularly if you are working on the RMI-IIOP code (which is tested in the IBM test suite). These tests are also descibed briefly in <ws>/docs/TestCases.sxc, which is a spreadsheet that roughly counts the number of test cases in each test in each test suite.
Each of the tests in the test suite starts one or more Controllers. A Controller is simply a class that controls a component of a test. For example, many of the tests have 3 controllers: a Client, a Server, and ORBD (which is used mainly for name service, as few tests actually exercise server activation).
Adding a new test is simple: just create a new package for testing, write the test, and add it to the appropriate test file. There is a document that is somewhat helpful in the workspace at
Especially read the section on the Controller classes, as that is really the heart of the test framework. However, the document is old, and somewhat out of date. You should consider the following additions and changes since the document was written:
The following tests currently use JUnit:
}
Setting the more secure policy file is useful to work on ensuring that the ORB has doPrivileged blocks around all operations that have security implications. It is known that the ORB is at least somewhat deficient in this area, but we have not taken the time to thoroughly address this issue.
To debug a test, you need to know the name of the controller(s) to which you need to attach a debugger. Controllers are normally created by the methods createORBD, createClient, and createServer. The default names of the controllers are ORBD, Client, and Server, respectively. The createClient and createServer methods can also take a second argument (the first is the class name of the test program) that gives a specific name for the controller.
Given the name of the controller(s) to debug, simply add the argument
for controllers XXX and YYY (for example) to the end of the test file argument that starts the test.
ORB debug flags can also be passed into a test. To do this, add the argument
where the argument is a semi-colon separated. Each element of this list starts with a controller name, followed by a comma separated list of ORB debug flag names (see com.sun.corba.ee.spi.orb.ORB for the current list).
It is also possible to change the log levels so that ORB log information can be displayed on the console (or anywhere else, depending on the log system configuration). This follows the usual log system mechanisms. The ORB logger names are discussed in sec:ORB-Logging.
I recently added the capability to run and debug a single test. This requires the runtest script in the make directory. This is a script that calls the run-test-target target in the test.xml file. You can give runtest any arguments that occur in the .tdesc files. To use runtest, cd to <ws>/make and check that runtest is executable. Here are a few examples (assuming the current directory is <ws>/make):
All tests are run in the environment of the current ORB workspace. Either the renamed or the non-renamed version can be used. This is important because JDK 5 or later contains many of the same classes in the com.sun.corba packages as the workspace. The ant files set up the java -Xbootclasspath argument correctly so that the versions of the com.sun.corba classes in the workspace are used, instead of those in the JDK.
Sony Manuel maintains a large collection of CORBA SQE tests in /java/idl/ws/rip/RIP_TEST_MASTER. Read the file DTF_RTM_README.html in this workspace for details (and contact Sony as well). These test are not currently available in GlassFish-CORBA.
We should look at creating a tighter integration between the CORBA dev tests and the SQE tests at some point. In particular, I’d like to have the POA and INS tests run automatically as part of the CORBA dev test cycle.
Middleware is a rather complex kind of software to build well. It spans many parts of the computer science discipline, including compilers, operating systems, and network communications. Middleware tends to be complex, long-lived software, and there are many different ways to build it. Our goal in developing the ORB has been to develop very flexible and high-performance middleware while maintaining a clear (if complex) architecture that can be easily composed, ultimately out of re-usable modules. One way to look at an ORB is that ORB.init creates a particular middleware implementation that is specialized to the needs of an application. For example, the behavior of the default ORB in the JDK is rather different from that of the ORB in the app server, even though both share >95% of the same code.
At least the following dimensions must be considered in order to build effective middleware:
Examples of data types include the IDL data model from CORBA, the Java data model from RMI, XML schema, ASN.1, SUN RPC, MIME types, and many others.
There are broadly speaking two ways to view the API question: either the system uses some sort of Proxy to make a remote call “look like” a call to an abstraction (method call, procedure call, SmallTalk message send, etc) or the API provides an explicit representation of a request or message that a program can set up with data and then send (CORBA dynamic invocation, Message Oriented Middleware, and others).
Today, our latency is poor (except for co-located RMI-IIOP calls, which are highly optimized), our throughput is OK (and this is NIO select related), and the IOR handling is pretty good, although much more is possible.
We need to follow a number of strategies for improving performance:
This is particularly important for systems that have extreme flexibility.
The solution here is to use WeakHashMap (for Classes in keys) or SoftCache (for Classes in values) as need. Grep the source code for examples.
The main body of the ORB code (that is, most of the contents of the src directory) must be delivered into both the JDK and the app server without change (other than the automatic rename). This has some interesting implications:
Note that there is currently NO plan to integrate the GlassFish v3 ORB into JDK 7: there simply are not enough people working on CORBA to support this. This will lead to an increasing divergence over time between the GFv3 and JDK ORBs, particularly in the following areas:
The ORB class is the central control point in the Sun ORB implementation. Here we will examine its structure, the services it provides, and how it is initialized and terminated.
The ORB class has the longest inheritance chain in the ORB. Constructing a UML diagram for this is difficult (because of the way this works in Java Studio Enterprise) and not very illuminating. Instead, I’ll just list the classes and their main function:
The first 3 classes together define the standard OMG ORB API. It is split because it evolved in stages, and we cannot add new methods to an interface that a third party may implement. Instead, we extend the API class, allowing older ORB implementations from third parties to continue to work. The classes in the internal package can be ignored. They are present so that any old code written with the ORBClass property set to the class name will continue to work.
The key part of the ORB SPI is in the com.sun.corba.ee.spi.orb.ORB abstract class. This is an abstract class because it must extend the other ORB abstract API classes defined by the OMG. The ORB class provides the following operations:
This allows us to extend the basic IOR framework with tagged component and profiles as needed, and also allows us to plug in a particular object key representation. The object key representation is important for the operation of object adapters.
Clearly there are a lot of methods in the ORB SPI. A better approach would be to push most of this into the initial references mechanism, using IDL local objects. This would substantially reduce the size of the ORB SPI. But I think we are unlikely to do this, as the impact on the existing code is probably too large.
The ORB initialization code is fairly complicated, but reasonably well structured at this point. I’ll start with a discussion of the ORB configuration framework, then describe the ORB initialization process in moderate detail. I’ll also discuss how this is extended in the app server.
The orb packages contains a mostly general purpose framework for handling configuration. This is divided into several parts:
The following sections will look at these parts in more detail.
A DataCollector has a rather simple interface: setParser passes a PropertyParser instance to the DataCollector, which causes the DataCollector to gather together all configuration data from the available data sources into a single instance of Properties. This instance is available in the getProperties method.
The available data sources are:
It is perhaps not immediately obvious why a PropertyParser needs to be passed to the DataCollector. The reason for this is that it is not always possible to simply grab every bit of information from the data sources. But it is possible to get all configuration information for the known property names. So the DataCollector uses the PropertyParser to fetch information for all property names of interest.
Here is a class diagram of the DataCollector classes:

Note that there are 3 different kinds of DataCollector. The Applet and Normal DataCollectors are used with the corresponding ORB.init methods. The PropertyOnly DataCollector is only used internally, when we need to create an internal full ORB instance to support certain operations on the ORB singleton. The DataCollectorFactory class provides static methods for creating the different kinds of DataCollectors.
There are a few issues here that could be revisited:
Fixing these issues would allow reuse of the DataCollector mechanism outside of the ORB, and provide a somewhat cleaner implementation.
The operation interface is simply:
}
The SPI classes OperationFactory and OperationFactoryExt provide a large number of factory methods for creating instances of the Operation interface:
Other Operation implementations can be readily created, but this set is sufficient to handle all ORB configuration parsing (except for URL parsing, which is currently handled by some classes in the resolver package). Extending this framework to handle URL parsing is relatively straightforward, but requires the ability to handle optional data and alternate forms that is not currently present (essentially something like ifAction( predicate, opTrue, opFalse ) would probably take us in the right direction).
The other issue with this is that composing all of the actions in Java is somewhat cumbersome (take a look at ParserTable.makeADOperation for an example). A customized language (e.g. some Lisp macros) could make this much simpler. Combining this with annotation and code generation could reduce the ORB configuration implementation to something like:
public interface ORBData {
public String getORBInitialHost() ;
...
}
Pursuing this degree of automation is probably more than is justified by the needs of the ORB.
The DataCollector gives us a way to gather multiple sources of configuration together into a uniform Properties object, and the Operation framework gives us a way to parse Strings into data in many different ways. The PropertyParser ties these two mechanisms together so that we can parse all of the configuration data in a single operation.
A PropertyParser is basically a collection of ParserActions. There are two kinds of ParserActions:
There is a factory class (ParserActionFactory) that is used to create the two ParserActions.
A PropertyParser is initialized by call its add and addPrefix methods to add the ParserActions that are needed in the PropertyParser. These methods return the PropertyParser so that they can be chained if necessary. Each of these methods takes the following arguments:
There are two other important methods in the PropertyParser. The parse method takes a Properties instance and returns a Map from fieldNames to the parsed values. This is the main parsing method in the framework. The iterator method returns an Iterator over the ParserActions, which can be used to find all of the property names in the PropertyParser. This is used by the DataCollector to determine which properties are required.
The following diagram shows the different classes used in the PropertyParser implementation:

The top of the configuration framework includes classes that can take the Map returned from a PropertyParser and use it to update the fields in a class that is essentially a read-only JavaBean. This is done in ParserImplBase. A further extension to this class in ParserImplTableBase allows the use of a table of ParserData to initialize the Parser.
The ORB configuration data is represented by the ORBData class. It
is implemented by ORBDataParserImpl, which extends ParserImplTableBase
and uses ParserTable (essentially a large ParserData[]) to provide
the initialization data.
The ParserData contains the following information:
Instances of ParserData are created by the factory methods in ParserDataFactory. ParserData is implemented by the NormalParserData and PrefixParserData classes.
Initializing an ORB from an ORB.init() call (the two versions that have arguments) requires several steps:
These steps are all standard. The more interesting part is what happens in set_parameters.
set_parameters proceeds as follows:
Most of the detailed ORB initialization happens in the ORB configurator, which we will examine next.
We have two mechanisms for customizing the ORB initialization: the standard (from PI) ORBInitializer, and the ORBConfigurator. Why two? There really is only need for one, except for one really irritating problem: the ORBInitializer does not provide direct access to the ORB or the ORB configuration data (our DataCollector). We also want to be able to have ORB extension parse configuration properties that are not even known in the base ORB configuration (although we don’t currently make use of this). So I chose to create the ORBConfigurator interface.
Looking back on this now, there is an alternative that may have been better: simply extend ORBInitInfo with an internal SPI so that we could access the ORB directly. The current situation is the result of a spec compromise: no one could agree on what operations should be allowed on an ORB instance while it’s in the process of initialization, so a facade object (ORBInitInfo) was specified that sharply restricts what can be done with the underlying ORB instance. Of course, this makes it hard to access anyone’s ORB extensions from inside an ORBInitializer.
The current ORBConfigurator we use is replaceable, as is obvious from the use of the parser to obtain it. For example, we could replace the current Java-code driven approach with an XML-based approach, a Lisp-Sexpression approach, something based on the JINI config language (which is an interpreted simple subset of Java), or some other mechanism. But this does not seem to be needed today.
Here is what the ORBConfiguratorImpl configure method does:
Much of this registration is driven by subcontract IDs. See sec:Subcontract-IDsfor more details.
TBD
portable interceptors
PEORBConfigurator
The ORBManager
The primary issue in starting up the ORB is simply to configure all of the data needed for running the ORB. In contrast, shutdown must carefully control access to the ORB so that spurious errors do not occur in requests that are in the middle of being processed.
Details TBD.
Here is a simplified sequence diagram describing the overall ORB dispatch path:
(discuss
this at a high level)
The next few chapters look at this in more detail through the PEPt model.
The presentation layer is largely concerned with two issues:
The APIs in the first bullet are largely about the stubs on the client side, and the skeletons on the server side. Demultiplexing is the job of the Object adapter (see 8.1).
The IDL to Java mapping and the Java to IDL mappings both define the architecture of stubs and skeletons. The overall stub architecture (static and dynamic) is described the Dynamic RMI-IIOP document.
The ORB supports all of the standard OMG data types that are available in the Java language mapping (see 5.3 for the details of the standard types, and their encodings in CDR. Note that CDR is only one possible encoding for the standard types).
A repository ID in CORBA is the on-the-wire representation of a type. Several forms are defined in the CORBA spec, and in fact the format is extensible, but we are only interested in two forms: the IDL and the RMI-IIOP repository IDs.
The IDL repository ID has the form IDL:<identifier list>:<major>.<minor>. The identifier list consists of a number of identifiers (typically as are used in IDL module and interface names) separated by “/”. This usually corresponds to the declaration used in the IDL source file.
The RMI repository ID has the form RMI:<class name>:<hash code>:<serialVersionUID>. The serial version UID is used (as usual) to determine whether two classes of the same name really have compatible representations. The hash code is unique to RMI, and allows the CDR protocol to determine whether or not the sender and the receiver have the same version of the class identifier by the class name. If the do, the receiver can use its local class declaration to unmarshal the received data, otherwise the receiver must use a special sending context object to obtain the sender’s version of the class. The receiver then executes an algorithm to match up the data sent with the class in its local environment.
Because the ORB uses NIO in its transport, it is necessary to use NIO ByteBuffers for managing the storage underlying the input and output streams. The ORB general uses direct ByteBuffers, which unfortunately are very expensive to create, and prone to lingering instead of being GCed promptly. Consequently the ORB managers most buffers in pools.
CDR is the standard binary data representation defined in the OMG CORBA standards. Our ORB uses this as its main on-the-wire representation for all communications. CDR is defined in terms of the IDL types, which are not the same as Java types. RMI-IIOP is defined by translating Java types into IDL types, so CDR also applies to RMI-IIOP. CDR primitives are all fixed in length, and aligned according to their size. This was originally done to make unmarshaling easier for 1980’s era RISC CPUs, but makes little sense today.
CDR supports both big endian and little endian byte orderings (and these are the only byte orderings still in common use, since other word oriented computers no longer exist).
Table 5.1: CDR data types
Data type EncodingAlignment Size Notes char1 wchar1 1-4 many different encodings octet unsigned 8 bit integer1 1 similar to java byte, but unsigned short BE/LE 2’s complement2 2 same as java short unsigned short BE/LE unsigned2 2 not in java long BE/LE 2’s complement4 4 same as java int unsigned long BE/LE unsigned4 4 not in java long long BE/LE 2’s complement8 8 unsigned long long BE/LE unsigned8 8 float BE/LE IEE 4884 4 same as java float double BE/LE IEE 4888 8 same as java double long double BE/LE IEE 4888 - not in java boolean1 enum as an unsigned long4 IDL enum struct by elementby element variable IDL struct union discriminant tag; branch elementsby element variable IDL union sequence same as array IDL sequence array unsigned long; array elements4 (for size) variable IDL array strings unsigned long; bytes; 04 (for size) variable IDL: many encodings fixed point -- - IDL (not commonly used) value type complex IDL value type, most Java types typecode complex IDL typecode any complex IDL any principal sequence<octet> deprecated context sequence<string> IDL context (deprecated) object reference abstract interface
We’ll look at the encoding of more complex types in the following sections.
An Object Reference (objref for short) is the CORBA type that represents a remote object. It contains transport endpoint address information, policy information that may affect how remote invocations are handled, and an identity for the remote object that identifies the object within the transport endpoint.
The specific structure of an objref is the IOR, which is represented in IDL as follows (see src/share/classes/org/omg/PortableInterceptor/IOP.idl, plus the code in com.sun.corba.ee.impl.io.IIOPProfileImpl and IIOPProfileTemplateImpl):
This is not exactly how all of this is declared: in particular, the ORB does not actually define ProfileBody this way. The spec (formal/02-06-01) defines ProfileBody_1_0 and ProfileBody_1_1 in section 15.7.2, but the only difference is the presence of components at the end. The ORB handles this by checking the version, and not allowing a sequence of components if the version is 1.0.
The IOR supports many different possible representations through different kinds of TaggedProfiles. Our ORB only creates IORS that contain a single TaggedProfile with tag TAG_INTERNET_IOP, so the profile_data is always an encapsulation of IIOPProfileBody. However, the ORB does not use the IDL IIOPProfileBody described above. The actual internal Java representation is given in com.sun.corba.ee.impl.ior.iiop.IIOPProfileTemplate. The write method is implemented as follows:
The IIOPProfileTemplate extends List<TaggedComponent>. In the ORB representation of IORs, both TaggedComponents and TaggedProfiles are instances of Identifiable, so that a common set of utilities can be used to write them out.
TaggedComponents
An abstract interface is a type in IDL that can either represent a value type or an object reference. It is frequently used in RMI-IIOP to represent a type whose most derived type is java.lang.Object. The encoding is simple: first a boolean indicating whether the following data is a value type (false) or an object reference (true). This is just an IDL union with a boolean discriminator type.
There are several places in the GIOP protocol where a marshalled representation of some data type is embedded as a sequence<octet> inside another type. This is referred to as a CDR encapsulation. The representation is simple: first a byte order marker (a single octet) is marshaled to indicate whether the encapsulation is big endian or little endian. Then the rest of the data is marshaled, using the appropriate alignment.
Typically encapsulation is used for the following data types:
Notes and thoughts?
The standard encoding that CORBA uses for all messages is called CDR. CDR (like XDR and many others) is a binary encoding that encodes primitive data types and various more complex data types. However, CDR may not always be the best choice, and we have experimented with other sorts of encodings.
One experiment is the Java Serialization for GIOP (JSG) protocol. This is simply the use of Java serialization instead of CDR to encode data types in GIOP messages. This did not turn out to have any performance advantages in our ORB, so we have never made significant use of JSG (and we will probably remove the code at some point).
Another experiment (still in progress) is parallel marshaling. This is intended to explore several key concepts:
The plan is that such techniques will make it possible to achieve order-of-magnitude improvements in marshaling speed, which are simply not possible with current protocols.
The GIOP protocol is the standard protocol used for CORBA in most application. GIOP is intended to be used over any reliable connection-oriented transport, but most typically it is used over TCP/IP, in which case the protocol is typically known as IIOP. Typically IIOP connections are persistent, and may be shared and re-used for many operations. Also, connection usage is asymmetrical: the request initiator opens the connection, and manages connection caching. The request receiver processes requests, and must send any replies on the same connection on which the request was received. Since any CORBA process may both originate requests and handle requests, it is possible (and common) for a receiver of a request to send a request back to the sender. In such cases, a new connection must be used (unless bi-directional GIOP is used, which we do not support).
Each GIOP message starts with a GIOP Message header, defined as follows for GIOP 1.2:
The fields are used as follows:
The Request message consists of a GIOP message header, a Request message header, and a request message body. The request header for GIOP 1.2 (ignoring target address mode) is encoded as:
The request body starts (for GIOP 1.2) on the next 8-byte aligned octet after the end of the request header. It contains all of the parameters (in declaration order) of the operation. Note that for idl this includes in and inout parameters. For RMI-IIOP this includes all parameters.
Like the request message, the reply message has 3 parts: a GIOP message header, a reply message header, and a reply body. The reply header (for GIOP 1.2) looks like:
The request_id is used to match request and reply, and also to find all fragments if the reply is fragmented. service_context contains any relevant information associated with the reply, just as in the request message. This is commonly added using portable interceptors.
The reply_status indicates the meaning of the reply, and also the encoding of the reply body as follows:
A cancel request message is sent by the client to the server to indicate that the request is no longer needed, and the server should stop processing the message (if it is still processing it) and discard resources assocaited with the request. It consists of a GIOP message header, and a cancel request header. The cancel request header contains only an unsigned long representing the request ID.
A locate request is used to determine if an object reference can handle a request, and if not, what object reference can do so. It consists of a GIOP message header followed by a LocateRequest header, which has the following form:
Note that the request uses a TargetAddress. Our ORB will only ever send a KeyAddr type here.
Our ORB rarely uses a LocateRequest, because in most cases it is better to first try the request (which can get exactly the same results as a LocateRequest, plus get the actual result) and avoid an extra round trip. We do use the LocateRequest for bootstrapping, such as in the case of resolving an INS URL into an object reference.
A locate reply is sent in response to a locate request. It consists of a GIOP message header, a locate reply header, and a locate reply body. The locate reply header is defined as follows:
As usual, the request_id is used for matching request and reply, and for handling fragmentation. The locate_status determine the result of the request as well as the locate reply body contents as follows:
The CloseConnection message indicates that the connection will be closed. It can be sent by either the client or the server. It consists of only the GIOP message header.
Message error is sent if the connection receives a message that cannot be interpreted for some reason. Basically this means that a message was received (either by the client or the server) that could not be interpreted, and so no subsequent message can be reliably interpreted either (if you can’t recognize the message type, you can’t determine its length, and so all subsequent message framing is lost). About the only action possible in this case is to close the connection, which results in errors on any pending request on the client side. The message consists of only the GIOP message header.
Fragments are used to deal with long messages. For example, our ORB uses a default fragment size of 4K bytes. But it must be possible to marshaling things like an array of 1 million longs (8 MB roughly in size). The only way to do this is with fragments.
Request, Reply, LocateRequest, and LocateReply messages may all be fragmented. Note that all of these messages have headers AFTER the GIOP message header that start with an IDL long (4 byte) request ID. The Fragment message itself starts with a GIOP message header, followed by a fragment header containing only an IDL long request_id. This means that essentially all GIOP (1.2!) messages start with a 12 byte GIOP header, followed by a 4 byte request ID. In essense, the request ID is “part” of the header, although the header was not defined that way initially. This is useful, because reading the first 16 bytes of a message (ignoring CancelRequest and MessageError, which are special cases) always tells us the type of the message, its size, and its ID. That is all the information needed to fully read the message from the transport, and dispatch it to the appropriate level of the code after the transport.
The subcontract concept dates back to the early 90s and the work in SunLabs on the Spring project. Spring was a project to design a new operating system, built completely along object-oriented ideas, that used a few powerful concepts to implement the operating system. Some of these ideas included:
The spring OS work is long gone from Sun at this point, but we have often found the subcontract concept to be useful as one layering mechanism in the ORB. It fits into the protocol part of the PEPt architecture.
Our ORB encodes a 4-byte subcontract ID in the object key in every IOR. However, we currently use the same classes (ClientRequestDispatcher on the client side, and CorbaServerRequestDispatcher on the server side) for every subcontract. But we do make good use of subcontracts for LocalClientRequestDispatchers, which support highly effective optimizations for invoking methods on co-located object references (that is, when the remote object reference is invoked inside the same JVM and ORB that created it).
GIOP messages are encoded in a conventional manner in the com.sun.corba.ee.impl.protocol.giopmsgheaders package. Each version of each type of GIOP message is represented as a separate class, with base classes as needed.
The transport is responsible for handling the transfer of data to and from endpoints. Connection management is also an important part of the transport, since we most commonly use GIOP as a protocol, and GIOP is connection based. We also include here the logic that is used to decide which of several possible endpoints should be used for a connection.
The client and the server roles in a CORBA request are distinct, but both are event driven: messages are normally received by a selector thread. The client simply needs to get a connection, write the messages to the connection, and wait for a response. The server is event driven: it responds to messages received. The server also contains acceptors, which represent endpoints on which the server listens for new connections,
include security document here (that I was working on for a while last summer)
This document was translated from LATEX by HEVEA.