Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

XML Reflection for CORBA


XML Reflection for CORBA

Introduction

Some programming languages, such as Java and Smalltalk, support reflection or introspection. This feature allows applications written in these languages to obtain information about objects and classes at runtime, typically using this information to perform dynamic invocations. This information about objects and classes is commonly referred to as metadata and includes information on the number of methods in a class, the method signatures, and the dynamic size of objects. Of course, dynamic CORBA applications are possible as well, as we explained in our four-part series covering Dynamic CORBA [1]. An application that wants to dynamically discover metadata about objects at runtime uses the Interface Repository (IFR) [2]. Dynamic clients invoke operations using the Dynamic Invocation Interface (DII) [1], while dynamic server applications handle requests via the Dynamic Skeleton Interface (DSI) [3].

As we explained in our Dynamic CORBA series, CORBA's reflection facilities are hard and tedious to use. This isn't too much of a problem in practice for CORBA applications themselves, however, as most are static. In particular, most CORBA applications are written in statically typed languages, such as C++ or Java, and they contain static stubs and skeletons generated by an IDL compiler. As a result, CORBA clients and servers generally already know all the type information they need to make and serve requests, which is in fact a part of CORBA's design center. For example, CORBA's General Inter-ORB Protocol (GIOP) is designed such that no type information is passed as part of a request or a reply, instead requiring the sending and receiving applications to know argument and return types a priori. (The only exception to this is the any type, which always includes type information in the form of a TypeCode.) CORBA is thus designed such that the IFR is never needed for most applications.

Unfortunately, the static nature of most CORBA applications can complicate their integration with other non-CORBA systems. Due to the proliferation of different middleware technologies, including CORBA, J2EE, .NET, and Web Services, the need to integrate disparate middleware systems is becoming more common. The weakness of the current economy is also contributing to this trend, as IT groups that were formerly autonomous and made their own middleware purchasing decisions are now being merged and consolidated. As a result, the different middleware systems they chose now have to be merged and consolidated too. The need to bridge different "middleware islands" is thus at an all-time high.

Accessing a CORBA object from any client application, whether it's a CORBA application or an application written using other middleware, requires knowledge of the details of the object's interface. These metadata details include its interface type, all of its operations, and the types of each operation's arguments and return value (client applications also have to understand the object's semantics, obviously, though there is currently no easy or generally accepted way to convey these from application to application). While you can obtain these details from the system's IDL definitions, it's atypical to keep IDL files handy for systems running in production.

The IFR is adequate for serving metadata to CORBA applications, but for several reasons, it's inadequate for non-CORBA applications. Foremost among these is its complexity. IFR metadata is, of course, defined in IDL, and its definitions form a complex hierarchical tree. Some of the types represent structured values, while others represent references to objects. The fact that all these types are all defined in the CORBA IDL module means that an application using them winds up with practically the entire CORBA module compiled into it. While this is not unusual for a CORBA application, it's often far too heavy of a solution for a non-CORBA application.

A more serious problem with the IFR is that few CORBA systems even deploy it. An IFR server is an extra process requiring additional configuration and maintenance, adding cost to deployed systems. It requires persistence so it can store all the necessary IDL definitions. Moreover, if an IFR instance is allowed to go out of synch with actual interface definitions of the objects in the system, it becomes useless. Thus, keeping an IFR up to date and synchronized with the objects' IDL definitions is an extra burden that few application developers and users have embraced in practice.

The IFR may be the only standard source of metadata in a CORBA system, but ironically, the necessary metadata is always there, even without an IFR, since it's inside every server application. In essence, the same metadata that CORBA servers need internally to demarshal and dispatch incoming requests to servants is exactly the same metadata that clients need to invoke requests on target objects. If there were only a way to export that metadata so that clients could easily access it, they would never need an IFR.

Server Metadata Access

One approach to making the server's built-in metadata available to clients is to implement the standard CORBA::Object::_get_interface to return a reference to an InterfaceDef object implemented by the server itself. The default implementation of _get_interface normally just delegates the request to the IFR, but because _get_interface is a virtual function inherited from PortableServer::ServantBase, an ordinary servant can override it. Unfortunately, while this approach avoids the IFR itself, it doesn't do much to reduce the complexity of the solution since it necessarily uses all the same complicated IFR type definitions like InterfaceDef and FullInterfaceDescription. Another problem with this approach is that implementing full IFR lookup and search semantics within each server is simply too hard for the average server developer, even if these IFR semantics are encapsulated within a single library shared across implementations. Finally, implementing just the portion you need and delegating the rest to the actual IFR is tedious because of all the objects involved. A similar approach is to add a new operation into the CORBA::Object interface, but that is too invasive and too costly.

An easier approach to providing direct access to built-in server metadata is to define a new IDL interface for accessing it. A server wishing to allow metadata access simply adds this interface to each object's interface inheritance hierarchy. This approach has several benefits:

  • Semantics of the operations that supply the metadata are specific to this interface, which prevents issues such as those associated with overriding the standard _get_interface operation described above.
  • There's no need to try to add new operations into the CORBA::Object interface, which is hard to do given that it would require months or even years of work in the OMG to make the necessary changes to the CORBA standard.
  • Determining in a client application whether a given target object supports the metadata interface is simple: the client attempts to narrow to that interface. If the narrow succeeds, the object supports the metadata interface.

The operations in such an interface could reuse the IFR InterfaceDef and all of its related types, but again, they're too complex, especially for non-CORBA applications. We could define similar but simpler types specifically within our IDL interface, but even these would be too complex for access from non-CORBA systems.

One solution to the metadata definition problem is XML. It continues to grow in popularity as a data definition language, mainly because of its flexibility and the ubiquity of the tools needed to parse it. An accessor interface that returns metadata in an XML format might look as follows:

// IDL
module Reflection {
   interface XMLProvider {
      string get_description();
   };
};

The simplicity of this interface is intentional, as it maximizes our chances that it could be used by non-CORBA applications, such as .NET or J2EE. First, we have the Reflection module, used to contain all definitions related to object metadata queries. Inside the module is the XMLProvider interface with its single get_description operation, which returns a string.

Using the interface described above, A CORBA client application that wanted to query an object for its metadata would look like this:

CORBA::Object_var obj = // get reference to object
Reflection::XMLProvider_var xml = Reflection::XMLProvider::_narrow(obj);
if (!CORBA::is_nil(xml)) {
  // object supports XML metadata access
}

Assuming the narrow succeeds, the client can use regular XML parsing tools such as DOM or SAX to parse the object's metadata.

Server Metadata

To provide an example of the metadata that XMLProvider might return to a client, let's recall our tried-and-true Stock::Quoter interface:

// IDL
module Stock {
   exception Invalid_Stock {};

   struct Info {
      long high;
      long low;
      long last;
   };

   interface Quoter {
      Info get_quote (in string stock_name) raises (Invalid_Stock);
   };
};

For an object to support both Quoter and XMLProvider, we need to create a new interface that inherits from both:

// IDL
interface ReflectiveQuoter : Stock::Quoter, Reflection::XMLProvider
{};

To support XML reflection, our quoter servant must implement this derived interface rather than just the base Quoter interface (following this example, though, we'll discuss an alternative that allows reflection to be added to an existing object without requiring any application code changes).

Before we can implement the XMLProvider::get_description operation, we need to decide what form the XML will take. The full XML Schema definition is too long to include here, but our chosen schema includes three major elements: an interfaceinfo element that contains one types element and one targetInterface element. The targetInterface element, which simply refers to one of the interfaces defined within the types element, identifies the most-derived interface of the target object. For our example, at first glance this interface is the ReflectiveQuoter interface. Including the XMLProvider interface within the metadata description is redundant, however, given that clients must already be aware of that interface before they can request the XML metadata. We'll therefore ignore the XMLProvider interface for our reflection purposes, and instead treat the original Stock::Quoter interface as the most-derived interface.

Type definitions with the XML types element closely resemble their IDL counterparts. For example, the InvalidStock exception is defined following our XML schema as follows:

<corba:exception name="type2"
      typeid="IDL:Stock/InvalidStock:1.0"
      idlname="InvalidStock"/>

The name attribute is a local name generated by the formatter to support type references. Both the typeid and idlname attributes reflect properties of the original IDL type, specifically, the exception's repository ID and its name. Note that because CORBA TypeCodes do not store fully scoped names, our idlname attribute is the exception's identifier rather than its full name. As a result, the idlname is not guaranteed to be unique within our XML document, which is why we use names generated specifically for this XML document as values for the name attributes.

More interesting is the Info struct's XML definition:

<corba:struct name="type3"
       typeid="IDL:Stock/Info:1.0" idlname="Info">
  <corba:member name="high">
    <corba:basictype type="corba:long"/>
  </corba:member>
  <corba:member name="low">
    <corba:basictype type="corba:long"/>
  </corba:member>
  <corba:member name="last">
    <corba:basictype type="corba:long"/>
  </corba:member>
</corba:struct>

As with the InvalidStock exception, the definition for Info mirrors its IDL definition. Essentially, the XML definition supplies all the information available at runtime within the struct's TypeCode.

The XML definition for the Quoter interface is a little more involved. It must contain definitions for all base interfaces and for all operations and attributes. In our XML schema, we do not support attributes directly, but we instead treat them as pairs of get/set operations (or just a single get operation for read-only attributes). This design is again intended to make the XML more understandable to non-CORBA applications.

<corba:interface name="type1"
      typeid="IDL:Stock/Quoter:1.0" idlname="Quoter">
  <corba:operation name="get_quote">
    <corba:return>
      <corba:typeref typename="tns:type3"/>
    </corba:return>
    <corba:param name="stock_name" direction="in">
      <corba:anontype>
        <corba:string/>
      </corba:anontype>
    </corba:param>
    <corba:raises>
      <corba:typeref typename="tns:type2"/>
    </corba:raises>
  </corba:operation>
</corba:interface>

Not unexpectedly, the form of the XML definition for Quoter is not too different from its IDL definition. It declares a single operation, get_quote. Within the operation element, the return type is declared as a typeref to the Info type defined earlier. The typeref elements used for the get_quote return type and for the raises type show how the local unique XML element names are used to reference types defined elsewhere in the XML document. The "tns:" prefix is an XML namespace used to scope the local type names generated for this document. The single parameter type for get_quote, a string, is properly noted as an unnamed anontype (anonymous IDL type).

The targetInterface element for our metadata appears simply as follows:

<corba:targetInterface typename="tns:type1"/>

As before, a typeref element is used to reference the target interface definition, in this case a reference to the Quoter interface definition.

Implementing XMLProvider

The implementation of the XMLProvider interface within the server is fairly straightforward, but a bit tedious. We do not show the entire implementation here. Basically, what the implementation does is generate an XML string based on the properties of the most-derived interface (minus the XMLProvider interface itself) of the target. Most of the XML string is generated based on TypeCode contents for parameter and return types, but knowledge of base interfaces of the target interface is also needed to ensure that the entire transitive closure of the inheritance hierarchy is fully represented. It's straightforward to write a single function that can take data structures that describe all the interfaces, operations, parameters, and return types that make up a given target interface, and generate the XML string to be returned by XMLProvider.

Most CORBA server applications contain static skeletons. Adding XML metadata support to such applications can therefore be done trivially. If we augment the IDL compiler to generate the metadata for us into the skeletons, we need only regenerate the skeletons and recompile the server. This approach can add XMLProvider support to our existing objects without requiring any application-level changes or additions.

In our Quoter example above, we manually created a new derived interface called ReflectiveQuoter that mixed in the XMLProvider interface, but we can take a different approach if the IDL compiler is involved. The compiler knows that the skeleton for a given interface either derives from other skeletons when interface inheritance is involved, or derives from PortableServer::ServantBase if the interface has no base interfaces. To add support for the XMLProvider interface, therefore, the IDL compiler could simply generate each base skeleton class to derive from the XMLProvider skeleton where it would normally generate ServantBase derivation. The compiler then generates the implementation of the XMLProvider::get_description operation for each derived skeleton class. This method is declared pure virtual in the XMLProvider skeleton, but for each skeleton derived either directly or indirectly from the XMLProvider skeleton, the IDL compiler must override it and give it an interface-specific implementation.

In implementing this feature for IONA's Orbix, the XML formatting function was isolated into a function provided by one of the ORB libraries, and C++ data structures holding metadata descriptions were generated into the skeletons. The implementation of each skeleton's get_description method simply passes these data structures into the library function, which performs all the formatting and returns an XML string. This approach helps keep skeleton size to a minimum while avoiding duplication of the XML generation code.

Alternative Approaches

An interesting feature of our approach is that supporting other formats isn't easy. It requires implementing a new formatter for the desired format, and then for each server, telling the IDL compiler to use the new formatter. Obviously, this approach hard-wires a single format into each object.

To alleviate this problem, we could instead move the formatter choice over to the client side, such that a client could use the Factory Method pattern [4] to request whatever format they wanted. For example, one might add a Reflection::FormatFactory type as an object obtainable via CORBA::ORB::resolve_initial_references. Such a factory interface might look like this:

// IDL
module Reflection {
  local interface Formatter {
    exception NoMetadata {};
    string get_description(in Object obj)
      raises(NoMetadata);
  };

  typedef short Format;
  const Format XML = 1;
  const Format IDL = 2;
  // etc.

  local interface FormatFactory {
    Formatter create_formatter(in Format fmt);
  };
};

A CORBA client might use this as follows:

CORBA::Object_var obj =
  orb->resolve_initial_reference("ReflectionFormatFactory");
Reflection::FormatFactory ff =
  Reflection::FormatFactory::_narrow(obj);
Reflection::Formatter fmt =
  ff->create_formatter(Reflection::XML);
CORBA::String_var xml = fmt->get_description(some_object);

In essence, this approach hides whatever format the object uses for its metadata, and instead returns the format that the client desires. One problem with this approach, though, is that we now have to agree on a standard format for the metadata returned by the object, which requires defining a special metadata interface to be used on the server side that returns "raw" metadata. While this approach would work, and would yield the ability to support multiple formats, a "middle ground" variation might be even better. For example, we could modify the original solution to supply a generic provider, as follows:

// IDL
module Reflection {
  typedef short Format;
  const Format XML = 1;
  const Format IDL = 2;

  interface Provider {
    exception FormatNotSupported {
      Format f;
    };
    string get_description(in Format f)
      raises(FormatNotSupported);
  };
};

In this approach, we no longer support an XMLProvider, but instead support a general Provider whose get_description operation takes one parameter to indicate the desired format. Should a given Provider lack support for a requested format, it would raise the FormatNotSupported exception. Note, however, that with this approach all supported formats must be suitable for representation in the returned string type. One way to loosen this restriction, of course, is to make the return type an any, thus permitting virtually any format to be returned, but that seems like overkill. From a practical point of view, it's unlikely that many different formats will ever be needed.

Yet another alternative is to maintain the XML format as what's provided by the objects, keeping the desired simplicity for non-CORBA clients, but allow CORBA clients to obtain a transformer object from resolve_initial_references to turn the returned XML string into something like an InterfaceDef. The beauty of this approach is that it's entirely layered over the original XML solution. Some might label it as wasteful, though, given the conversions that occur both in the server and in the client, but retrieving metadata is rarely a performance-critical operation.

Unfortunately, these alternative approaches conflict with the goal of keeping the client interface as simple as possible, which is key to maximizing the ability for non-CORBA applications to use it. Given the ubiquity of XML and tools for parsing it and transforming it, returning XML-formatted metadata, as in the original approach, allows clients who need another format to simply transform the XML to whatever form they might need.

Concluding Remarks

In this column we've shown an alternative approach to supporting reflection in CORBA. The basic approach involves having each object returns its own metadata, rather than relying on an external service, such as the IFR. In practice, few CORBA systems even deploy the IFR, making it hard to integrate such deployments with non-CORBA systems after the system is already deployed. With the approach we've explained here, each object is capable of returning its own reflection metadata, guaranteed to be accurate and up to date, to clients. We've used XML as the metadata format to maximize its usefulness for non-CORBA applications, such as J2EE, .NET, and Web Services.

As always, if you have any comments on this column or any previous one, please contact us at [email protected].

References

1. Steve Vinoski and Douglas C. Schmidt, "Dynamic CORBA: Part 1, The Dynamic Invocation Interface," C/C++ Users Journal, July 2002.
2. Steve Vinoski and Douglas C. Schmidt, "Dynamic CORBA: Part 4, The Interface Repository," C/C++ Users Journal, January 2003.
3. Steve Vinoski and Douglas C. Schmidt, "Dynamic CORBA: Part 3, The Dynamic Skeleton Interface," C/C++ Users Journal, November 2002.
4. Gamma, Helms, Johnson, and Vlissides, "Design Patterns: Elements of Reusable Object-Oriented Software," Addison-Wesley, 1995.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.