An IDL interface is just a collection of data attributes and methods that define the semantics of the interface. Declaring an interface is another way to create a new data type in IDL, but unlike structs and unions, an interface can have both data members and methods that can be called on objects of its type. An interface is also a name-scoping construct, similar to a module. You can declare an IDL interface and simply include a set of constants you want associated with that interface name. In this case, you have to specify the interface scope in order to refer to the constants from within other scopes.
An interface consists of the following elements:
// IDL interface identifier [: inheritance-spec] { interface-body };
The interface identifier can be any valid IDL identifier. The body of the interface can contain any of the following constructs:
A user-defined type (struct, union, typedef, enum)
A constant declaration
An interface-specific exception declaration
Data attributes
Methods or operations
We've already seen the syntax for the first three items in earlier sections of this IDL overview. They become part of an interface simply by being declared within the braces of the body of the interface. In the next few sections, we'll see how to define interface attributes and methods, and then we'll look at how inheritance of IDL interfaces works.
Attributes are data members that belong to an interface. To readers familiar with JavaBeans, declaring an attribute on an interface is roughly analogous to adding a property to a JavaBeans component. An attribute in an IDL interface indicates that the interface provides some way to read and (in most cases) write the attribute value.
The syntax for declaring an attribute within an interface body is:
// IDL [readonly] attribute type identifier [, identifier, ...];
The attribute is signified by the attribute keyword, followed by a type specification for the attribute and an identifier name. You can declare multiple attributes of the same type by providing their identifiers in a comma-delimited list after the type specifier:
// IDL attribute short coord_x, coord_y, coord_z;
The type specifier can be any valid type, including IDL basic types, other interfaces, and user-defined types previously defined or declared in a typedef. For example:
// IDL enum ErrorCode { BadValue, DimensionError, Overflow, Underflow }; interface AttrTest { struct coord { short x; short y; }; attribute ErrorCode lastError; readonly attribute coord COG; attribute string name; };
The optional readonly keyword can precede the attribute declaration. This indicates that the attribute can be read only externally and not directly written. This typically means that the value of this attribute is set only as a side effect of some other method(s). In our example, the COG attribute may represent the center-of-gravity of some geometric object, and we'll only want that to be recomputed as the result of other methods that change the geometry of the object.
>Methods (or operations, to use the IDL vernacular) provide a way for remote clients to interact with the objects defined by an interface. A method declaration in IDL is composed of an identifier for the method, the type of data returned by the method, and a list of parameters that the method accepts. An IDL method can also (optionally) be declared to use specific call semantics, to possibly raise certain exceptions during its execution, and to accept certain context variables from the client environment.
The syntax of a method declaration within an IDL interface is:
// IDL [call-semantics] return-type identifier ([param, param, ...]) [exception-clause] [context-clause];
The only required elements in a method declaration are the method identifier and the return type, so an example of the simplest form of method declaration is:
// IDL boolean doSomething();
This method simply returns a boolean flag when it is complete. It doesn't accept any arguments, uses the default call semantics, raises no nonstandard exceptions, and accepts no context variables from the client environment.
The return type for an IDL method can be any valid type, including user-defined types such as structs and other interfaces. If a method doesn't return any data, the return type should be declared as void.
The identifier for a method is a valid IDL identifier. In IDL, two methods in the same interface cannot have the same identifier (i.e., there is no method overloading, as there is in C++ and Java).
The parameters for a method on an interface are declared within the parentheses following the method identifier and are separated by commas. The syntax for an individual method parameter is:
arg-direction arg-type identifier
The identifier is any valid IDL identifier, and the parameter type is any valid IDL type, including user-defined types.
The direction specification indicates whether the parameter is passed into the server, returned from the server, or both. The direction specification can have one of three values: in, out, or inout. An parameter tagged as in is only passed from the client to the server object. An parameter tagged as out is not taken from the client, but its value is set by the server and returned if the method returns successfully. An inout parameter is passed in both directions; the data from the client is passed to the server, and the server may modify the data and have the updates returned back to the client if the method returns successfully.
Here's a modified method declaration for doSomething() that specifies some parameters:
boolean doSomething(in string whatToDo, inout string whatToDoItTo);
The first parameter tells the server object what to do, so it is input-only. The second parameter is the thing to be acted upon, so it is declared as inout to allow the modified object to be passed back to the client.
If a method raises an exception during its execution, the values of any out or inout parameters to the method are undefined. They may or may not have been modified by the method before the exception was raised, and execution was halted.
If a method on an interface can raise any exceptions during its execution, you have to declare this in IDL by adding a clause to the method declaration that lists all the exceptions that can be raised by the method. This is similar to the throws clause on Java methods. The syntax for the raises clause looks like:
// IDL raises (exception-type, exception-type, ...)
Every exception you list in this clause has to be defined earlier in the IDL file.
Every method that you declare on an IDL interface can potentially throw one of the standard ORB exceptions we mentioned earlier (see Table 10-4). You cannot list these standard exceptions in the raises clause for your methods.
As an example, let's specify a BadDirective exception for our doSomething() method, which is raised if the client passes in a string directive the server object doesn't understand. We can modify the method declaration to look like the this:
// IDL boolean doSomething(in string whatToDo, inout string whatToDoItTo) raises (BadDirective);
Again, we must have declared the BadDirective exception and any data it contains earlier in the IDL file.
IDL supports the concept of a client context, which can contain name/value pairs that describe the client's environment in some way. You might have an authenticated username stored in the client's context, for example. The name of a context value is a string, and its value is an Any object. The interface to the context is provided by the IDL Context interface, and a mapping of this interface must be provided in any language-specific binding of the CORBA standard.
You can add a context clause to your method declarations that indicates which client context variables should be propagated to the server when the method is invoked. The server object can then query these context variables during the execution of the method. The syntax for adding a context clause to your method declaration is:
// IDL context (var-name, var-name, ...)
Each var-name is a string literal that names the context variable to be propagated to the server when the method is called.
Suppose that when we invoke our doSomething() method, we want to be able to log who is making the request. We can look for a username variable in the client context and assume it is the authenticated identity of the client. We can specify that this context variable should be included in the method call by adding a context clause to our method declaration:
// IDL boolean doSomething(in string whatToDo, inout string whatToDoItTo) raises (BadDirective) context ("username");
A Java client might use this method like so:
// Java // Get the context Context ctx = ORB.get_default_context(); // Add a username to the context Any username = new Any(); username.insert_string("JimF"); ctx.set_one_value("username", username); // Call the remote method obj.doSomething("anything", "entity");
Since we declared the doSomething() method to include the username context variable in its invocations, this variable appears in the server's context and can be queried during execution of the method.[5]
[5] Sun's implementation of the Java IDL binding (including its idltojava compiler) does not support context variables. The Context interface is available in the Java IDL API, but context clauses on IDL methods are not represented in the generated Java code, and no context data is transferred to the server.
You might wonder when this context feature should be used, as opposed to just adding a method argument to the method declaration. I could have just as easily added another string argument to my declaration for the doSomething() method:
boolean doSomething(in string whatToDo, inout string whatToDoItTo, in string username) raises BadDirective;
One argument for using context variables is to make things easier on the client when certain data for a method is optional. Rather than including an explicit argument and forcing the user to add a nil value of some kind to the method call (null in Java, for example), you can make the optional data a context variable, and the user can choose to set it or not. In most cases, though, you'll find that context variables are used rarely, if at all.
If you don't specify any call semantics at the start of your method declaration, the default semantics is "at-most-once." This means that if a method call returns with no exceptions, the method was called a single time on the server object. If an exception is raised, the method was called at most once (the exception occurred either before the method was invoked, or during execution of the method).
You can choose to use alternate call semantics for your method by including a call attribute at the start of your method declaration. In the current CORBA standard, only a single alternative, called "best-effort" semantics, is available. In this case, whether the method call returns successfully or not, there's no guarantee that the method was actually invoked on the server object. The difference between the default semantics and "best-effort" semantics is roughly equivalent to the difference between TCP and UDP IP network connections and their handling of data packets.
You specify best-effort call semantics by adding the keyword oneway to the start of your method declaration:
// IDL oneway void tryToDoSomething(in whatToDo);
If you specify that a method is oneway, the return type of the method has to be void, and it can't have any out or inout arguments. The method is effectively called asynchronously, so the client can't synchronously receive return data from the server object.
You can inherit attributes and methods from another IDL interface by deriving your interface from it. The syntax for declaring the inheritance of an interface in its header is:
interface identifier : parent-interface, parent-interface, ... {
The parent interfaces can be any pre-defined interfaces, in the same module as this interface or in different modules. If the parent interfaces are from other modules, you need to use the :: scope specifier to identify them.
A derived interface inherits all the attributes and methods from its parent interfaces. Although IDL allows for multiple inheritance, it's illegal to have two inherited attributes or methods with the same identifier. You also can't declare an attribute or method within your interface with the same name as an inherited attribute or method (i.e., you cannot overload a method or attribute). Say you have two interfaces declared as follows:
// IDL interface A { boolean f(int float x); }; interface B { void f(); };
You cannot define a new interface that derives from both these interfaces, since the definition of the method f() would be ambiguous. Note that, unlike C++ and Java, IDL only uses the name for the method as its unique identifier, and not the entire method signature. This rule is a result of IDL's multilanguage support, since some languages may be similarly limited.
A derived interface also inherits any constants, user-defined types, and exceptions defined in its parent interfaces. They can be referred to in the derived interface as if they had been defined within the interface. For example, say we define the following base interface:
// IDL interface Server { exception ServiceInterrupted {}; boolean doSomething(in string what) raises (ServiceInterrupted); };
We can use the ServiceInterrupted exception defined within the Server interface in another interface by naming its scope:
// IDL interface PrintServer { boolean printSomething(in string what) raises (Server::ServiceInterrupted); };
Alternately, we can derive the PrintServer from the Server interface, and then the exception can be used as if it existed in the PrintServer scope:
// IDL interface PrintServer : Server { boolean printSomething(in string what) raises (ServiceInterrupted); };
It is legal to define a constant, type, or exception in a derived interface that uses the same name as one of these things in its parent interface. If you do this, though, you need to refer to them unambiguously in your interface declaration, using fully scoped names if necessary. If you declare your own ServiceInterrupted exception in the PrintServer interface, for example, you need to provide a scope for the exception in the raises clause, in order for the IDL compiler to know which version you're referring to:
// IDL interface PrintServer : Server { exception ServiceInterrupted { string printerName; }; boolean printSomething(in string what) raises (PrintServer::ServiceInterrupted);
If you don't, the IDL compiler throws back an error about ServiceInterrupted being ambiguous.
It's important to realize that IDL does early binding of constants, user-defined types, and exceptions as it compiles your IDL. This means that the definition of a constant, type, or exception is bound to a particular reference within an interface as it's encountered in your IDL file, not after all definitions have been examined. Consider the following IDL definitions:
// IDL struct Coord { short x; short y; }; interface GeometricObj { attribute Coord cog; }; interface GeometricObj3D : GeometricObj { struct Coord { short x; short y; short z; }; attribute Coord cog3D; };
The cog attribute in the GeometricObj interface is off the global Coord type (with x and y members only), since at the time the cog attribute is encountered in the IDL file, this is the binding definition for Coord. The GeometricObj3D interface inherits this attribute with this type. However, the cog3D attribute declared in the GeometricObj3D interface is of the GeometricObj3D::Coord type (with x, y, and z members), since at that point, the Coord struct within the GeometricObj3D scope has been defined and is the default reference of the relative Coord type used in the cog3D declaration.
As you might expect, each interface you define in IDL is mapped to a public interface in Java. Helper and holder class are also generated for each interface; the names of these interfaces are generated using the identifier of the IDL interface, with Helper and Holder appended to it.
The Java interface extends the org.omg.CORBA.Object interface. Any inheritance specification you provide in your IDL interface is mapped directly to interface inheritance in Java, using extends clauses. So our earlier GeometricObj3D example that inherits from GeometricObj is mapped into a Java interface that begins:
// Java public interface GeometricObj3D extends org.omg.CORBA.Object, GeometricObj { ...
The helper class generated for an interface includes a static narrow() method that allows you to safely cast a CORBA Object reference to a reference of the interface type. If the Object isn't of the expected type, an org.omg.CORBA.BAD_PARAM exception is thrown. The helper class also includes other static methods that let you read or write objects of the interface type over I/O streams and insert/extract an object of this type from an Any value.
The holder class is used whenever the interface is used as the type for an out or inout method parameter. The holder class is responsible for marshalling the contents of the object to the server object for the method call (for inout arguments), and then unmarshalling the (possibly updated) return value. The holder class has a constructor defined that lets you wrap the holder around an existing instance of the original interface, and it has a public value member that lets you access the object argument both before and after the method call.
See Chapter 4, "Java IDL" for more details on helper and holder classes.
Each attribute you declare on the IDL interface is mapped to two accessor methods, with the same name as the attribute. So an attribute declared within an IDL interface as follows:
// IDL attribute string name;
is mapped to these two methods on the corresponding Java interface:
// Java String name(); void name(String n);
If you include the readonly tag in your IDL attribute declaration, the Java interface has only the read accessor method, not the update accessor.
Methods declared on your IDL interface are mapped one-to-one to methods on the Java interface. The return values and any in parameters are mapped directly to their corresponding types in Java. Any out or inout parameters in the IDL method are mapped to their holder classes in the Java method. This includes basic IDL types, which have their own holder classes defined for them in the standard Java mapping. So this IDL method:
// IDL boolean setPrintServer(in PrintServer server, out PrintServer previousServer, out long requestsHandled);
is mapped to the following Java method on the corresponding interface:
// Java boolean setPrintServer(PrintServer server, PrintServerHolder previousServer, IntHolder requestsHandled);
Note that the last argument is declared a long in IDL, which is mapped to int in Java, so the IntHolder class is used in the mapped Java method.
To use this method, we have to create holder objects for the output parameters, then check their values after the method call:
// Java PrintServer newServer = . . .; PrintServerHolder prevHolder = new PrintServerHolder(); IntHolder numReqHolder = new IntHolder(); xxx.setPrintServer(newServer, prevHolder, numReqHolder); int numReq = numReqHolder.value; PrintServer prevServer = prevHolder.value;
We don't need to initialize the contents of the holders, since they are being used for out parameters. If they were used for inout parameters, we'd either have to initialize their contents at construction time or set their value members directly.
If there is a raises clause on your IDL method declaration, it is mapped to an equivalent throws clause on the Java method. The context clause and call semantics (oneway) on an IDL method declaration affect only the implementation of the generated Java method, not its signature.
Copyright © 2001 O'Reilly & Associates. All rights reserved.
This HTML Help has been published using the chm2web software. |