VisiBroker for Java Developer’s Guide : Using the Dynamic Invocation Interface

Using the Dynamic Invocation Interface
The developers of most client programs know the types of the CORBA objects their code will invoke, and they include the compiler-generated stubs for these types in their code. By contrast, developers of generic clients cannot know what kinds of objects their users will want to invoke. Such developers use the Dynamic Invocation Interface (DII) to write clients that can invoke any method on any CORBA object from knowledge obtained at runtime.
What is the dynamic invocation interface?
The Dynamic Invocation Interface (DII) enables a client program to invoke a method on a CORBA object whose type was unknown at the time the client was written. The DII contrasts with the default static invocation, which requires that the client source code include a compiler-generated stub for each type of CORBA object that the client intends to invoke. In other words, a client that uses static invocation declares in advance the types of objects it will invoke. A client that uses the DII makes no such declaration because its programmer does not know what kinds of objects will be invoked. The advantage of the DII is flexibility; it can be used to write generic clients that can invoke any object, including objects whose interfaces did not exist when the client was compiled. The DII has two disadvantages:
The DII is purely a client interface. Static and dynamic invocations are identical from an object implementation's point of view.
You can use the DII to build clients like these:
Bridges or adapters between script environments and CORBA objects. For example, a script calls your bridge, passing object and method identifiers and parameter values. Your bridge constructs and issues a dynamic request, receives the result, and returns it to the scripting environment. Such a bridge could not use static invocation because its developer could not know in advance what kinds of objects the script environment would want to invoke.
Generic object testers. For example, a client takes an arbitrary object identifier, looks up its interface in the interface repository (see “Using Interface Repositories”), and then invokes each of its methods with artificial argument values. Again, this style of generic tester could not be built with static invocation.
Note
Clients must pass valid arguments in DII requests. Failure to do so can produce unpredictable results, including server crashes. Although it is possible to dynamically type-check parameter values with the interface repository, it is expensive. For best performance, ensure that the code (for example, script) that invokes a DII-using client can be trusted to pass valid arguments.
Introducing the main DII concepts
The dynamic invocation interface is actually distributed among a handful of CORBA interfaces. Furthermore, the DII frequently offers more than one way to accomplish a task—the trade-off being programming simplicity versus performance in special situations. As a result, DII is one of the more difficult CORBA facilities to grasp. This section is a starting point, a high-level description of the main ideas.
To use the DII you need to understand these concepts, starting from the most general:
Request objects
Any and Typecode objects
Request sending options
Reply receiving options
Using request objects
A Request object represents one invocation of one method on one CORBA object. If you want to invoke two methods on the same CORBA object, or the same method on two different objects, you need two Request objects. To invoke a method you first need the target reference, an object reference representing the CORBA object. Using the target reference, you create a Request, populate it with arguments, send the Request, wait for the reply, and obtain the result from the Request.
There are two ways to create a Request. The simpler way is to invoke the target object's _request method, which all CORBA objects inherit. This does not, in fact, invoke the target object. You pass _request the IDL name of the method you intend to invoke in the Request, for example, “get_balance.” To add argument values to a Request created with _request, you invoke the Request's add_value method for each argument required by the method you intend to invoke. To pass one or more Context objects to the target, you must add them to the Request with its ctx method.
Although not intuitively obvious, you must also specify the type of the Request's result with its result method. For performance reasons, the messages exchanged between the VisiBroker ORBs do not contain type information. By specifying a place holder result type in the Request, you give the VisiBroker ORB the information it needs to properly extract the result from the reply message sent by the target object. Similarly, if the method you are invoking can raise user exceptions, you must add place holder exceptions to the Request before sending it.
The more complicated way to create a Request object is to invoke the target object's _create_request method, which, again, all CORBA objects inherit. This method takes several arguments which populate the new Request with arguments and specify the types of the result and user exceptions, if any, that it may return. To use the _create_request method you must have already built the components that it takes as arguments. The potential advantage of the _create_request method is performance. You can reuse the argument components in multiple _create_request calls if you invoke the same method on multiple target objects.
Note
There are two overloaded forms of the _create_request method: one that includes ContextList and ExceptionList parameters, and one that does not. If you want to pass one or more Context objects in your invocation, and/or the method you intend to invoke can raise one or more user exceptions, you must use the _create_request method that has the extra parameters.
Encapsulating arguments with the Any type
The target method's arguments, result, and exceptions are each specified in special objects called Anys. An Any is a generic object that encapsulates an argument of any type. An Any can hold any type that can be described in IDL. Specifying an argument to a Request as an Any allows a Request to hold arbitrary argument types and values without making the compiler complain of type mismatches. (The same is true of results and exceptions.)
An Any consists of a TypeCode and a value. A value is just a value, and a TypeCode is an object that describes how to interpret the bits in the value (that is, the value's type). Simple TypeCode constants for simple IDL types, such as long and Object, are built into the header files produced by the idl2java compiler. TypeCodes for IDL constructed types, such as structs, unions, and typedefs, have to be constructed. Such TypeCodes can be recursive because the types they describe can be recursive.
Consider a struct consisting of a long and a string. The TypeCode for the struct contains a TypeCode for the long and a TypeCode for the string. You can get a TypeCode at runtime from an interface repository (see “Using Interface Repositories”) or by asking the VisiBroker ORB to create one by invoking ORB::create_struct_tc or ORB::create_exception_tc.
If you use the _create_request method, you need to put the Any-encapsulated target method arguments in another special object called an NVList. No matter how you create a Request, its result is encoded as an NVList. Everything said about arguments in this paragraph applies to results as well. “NV” stands for named value, and an NVList consists of a count and number of items, each of which has a name, a value, and a flag. The name is the argument name, the value is the Any encapsulating the argument, and the flag denotes the argument's IDL mode (for example, in or out). The result of Request is represented a single named value.
Options for sending requests
Once you create and populate a Request with arguments, a result type, and exception types, you send it to the target object. There are several ways to send a Request,
The simplest is to call the Request's invoke method, which blocks until the reply message is received.
More complex, but not blocking, is the Request's send_deferred method. This is an alternative to using threads for parallelism. For many operating systems the send_deferred method is more economical than spawning a thread.
If your motivation for using the send_deferred method is to invoke multiple target objects in parallel, you can use the VisiBroker ORB object's send_multiple_requests_deferred method instead. It takes a sequence of Request objects.
Use the Request's send_oneway method if, and only if, the target method has been defined in IDL as oneway.
You can invoke multiple oneway methods in parallel with the VisiBroker ORB's send_multiple_requests_oneway method.
Options for receiving replies
If you send a Request by calling its invoke method, there is only one way to get the result: use the Request object's env method to test for an exception, and if none, extract the NamedValue from the Request with its result method. If you used the send_oneway method, then there is no result. If you used the send_deferred method, you can periodically check for completion by calling the Request's poll_response method which returns a code indicating whether the reply has been received. If, after polling for a while, you want to block waiting for completion of a deferred send, use the Request's get_response method.
If you have sent Requests with the send_multiple_requests_deferred method, you can find out if a particular Request is complete by invoking that Request's get_response method. To learn when any outstanding Request is complete, use the VisiBroker ORB's get_next_response method. To do the same thing without risking blocking, use the VisiBroker ORB's poll_next_response method.
Steps for invoking object operations dynamically
To summarize, here are the steps that a client follows when using the DII,
1
2
Create a Request object for the target object.
3
4
5
Example programs for using the DII
Several example programs that illustrate the use of the DII are included in the following directory:
<install_dir>/examples/vbroker/basic/bank_dynamic
These example programs are used to illustrate DII concepts in this section.
Using the idl2java compiler
The idl2java compiler has a flag (-dynamic_marshal) which, when switched on, generates stub code using DII. To understand how to do any type of DII:
1
2
Generate with -dynamic_marshal,
3
Obtaining a generic object reference
When using the DII, a client program does not have to use the traditional bind mechanism to obtain a reference to the target object, because the class definition for the target object may not have been known to the client at compile time.
The code sample below shows how your client program can use the bind method offered by the VisiBroker ORB object to bind to any object by specifying its name. This method returns a generic org.omg.CORBA.Object.
...
org.omg.CORBA.ORB orb;
org.omg.CORBA.Object account;
try {
// initialize the ORB.
orb = org.omg.CORBA.ORB.init(args, null);
} catch(Exception e) {
System.err.println ("Failure during ORB_init");
e.printStackTrace();
}
...
try {
// Request ORB to bind to the object supporting the account interface.
account = ((com.inprise.vbroker.CORBA.ORB)orb).bind("IDL:Account:1.0", "", "", null);
} catch(Exception excep) {
System.err.println ("Error binding to account" );
excep.printStackTrace();
}
System.out.println ("Bound to account object");
...
Creating and initializing a request
When your client program invokes a method on an object, a Request object is created to represent the method invocation. The Request object is written, or marshalled, to a buffer and sent to the object implementation. When your client program uses client stubs, this processing occurs transparently. Client programs that wish to use the DII must create and send the Request object themselves.
Note
There is no constructor for this class. The Object's _request method or Object's _create_request method are used to create a Request object.
Request interface
The following code sample shows the Request interface. The target of the request is set implicitly from the object reference used to create the Request. The name of the operation must be specified when the Request is created.
package org.omg.CORBA;
public abstract class Request {
public abstract org.omg.CORBA.Object target();
public abstract java.lang.String operation();
public abstract org.omg.CORBA.NVList arguments();
public abstract org.omg.CORBA.NamedValue result();
public abstract org.omg.CORBA.Environment env();
public abstract org.omg.CORBA.ExceptionList exceptions();
public abstract org.omg.CORBA.ContextList contexts();
public abstract void ctx(org.omg.CORBA.Context ctx);
public abstract org.omg.CORBA.Context ctx();
public abstract org.omg.CORBA.Any add_in_arg();
public abstract org.omg.CORBA.Any add_named_in_arg(
public abstract org.omg.CORBA.Any add_inout_arg();
public abstract org.omg.CORBA.Any add_named_inout_arg(
public abstract org.omg.CORBA.Any add_out_arg();
public abstract org.omg.CORBA.Any add_named_out_arg(
public abstract void set_return_type(
public abstract org.omg.CORBA.Any return_value();
public abstract void invoke();
public abstract void send_oneway();
public abstract void send_deferred();
public abstract void get_response();
public abstract boolean poll_response();
}
Ways to create and initialize a DII request
Once you have issued a bind to an object and obtained an object reference, you can use one of two methods for creating a Request object.
The following sample shows the methods offered by the org.omg.CORBA.Object interface.
package org.omg.CORBA;
public interface Object {
...
public org.omg.CORBA.Request _request(java.lang.String operation);
public org.omg.CORBA.Request _create_request(
org.omg.CORBA.Context ctx,
java.lang.String operation,
org.omg.CORBA.NVList arg_list,
org.omg.CORBA.NamedValue result
);
public org.omg.CORBA.Request _create_request(
org.omg.CORBA.Context ctx,
java.lang.String operation,
org.omg.CORBA.NVList arg_list,
org.omg.CORBA.NamedValue result,
org.omg.CORBA.ExceptionList exceptions,
org.omg.CORBA.ContextList contexts
);
...
}
Using the create_request method
You can use the _create_request method to create a Request object, initialize the Context, the operation name, the argument list to be passed, and the result. Optionally, you can set the ContextList for the request, which corresponds to the attributes defined in the request's IDL. The request parameter points to the Request object that was created for this operation.
Using the _request method
The code sample in “Example of creating a Request object” shows the use of the _request method to create a Request object, specifying only the operation name. After creating a float request, calls to its add_in_arg method add an input parameter Account name. Its result type is initialized as an Object reference type via a call to set_return_type method. After a call has been made, the return value is extracted with the result's call to the result method. The same steps are repeated to invoke another method on an Account Manager instance with the only difference being in-parameters and return types.
The req, an Any object is initialized with the desired account name and added to the request's argument list as an input argument. The last step in initializing the request is to set the result value to receive a float.
Example of creating a Request object
A Request object maintains ownership of all memory associated with the operation, the arguments, and the result so you should never attempt to free these items. The following code sample is an example of creating a request object.
// Client.java
public class Client {
public static void main(String[] args) {
if (args.length ! = 2) {
System.out.println("Usage: vbj Client <manager-name> <account-name>\
n");
return;
}
String managerName = args[0];
String accountName = args[1];
org.omg.CORBA.Object accountManager, account;
org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(args, null);
accountManager = ((com.inprise.vbroker.CORBA.ORB)orb).bind("IDL:Bank/AccountManager:1.0",
managerName, null, null);
org.omg.CORBA.Request request = accountManager.
_request("open");
request.add_in_arg().insert_string(accountName);
request.set_return_type(orb.get_primitive_tc(
org.omg.CORBA.TCKind.tk_objref)
);
request.invoke();
account = request.result().value().extract_Object();
org.omg.CORBA.Request request = account._request("balance");
request.set_return_type(orb.get_primitive_tc(
org.omg.CORBA.TCKind.tk_float)
);
request.invoke();
float balance = request.result().value().extract_float();
System.out.println("The balance in " + accountName + "'s account is
$" + balance);
}
}
Setting arguments for the request
The arguments for a Request are represented with a NVList object, which stores name-value pairs as NamedValue objects. You can use the arguments method to obtain a pointer to this list. This pointer can then be used to set the names and values of each of the arguments.
Note
Always initialize the arguments before sending a Request. Failure to do so will result in marshalling errors and may even cause the server to abort.
Implementing a list of arguments with the NVList
This class implements a list of NamedValue objects that represent the arguments for a method invocation. Methods are provided for adding, removing, and querying the objects in the list. The following code sample is an example of the NVList class:
package org.omg.CORBA;
public abstract class NVList {
public int count();
public void add(int flags);
public void add_item(java.lang.String name, int flags);
public void add_value(
java.lang.String name,
org.omg.CORBA.Any value,
int flags
);
public org.omg.CORBA.NamedValue item(int index);
public void remove(int index);
}
Setting input and output arguments with the NamedValue Class
This class implements a name-value pair that represents both input and output arguments for a method invocation request. The NamedValue class is also used to represent the result of a request that is returned to the client program. The name property is simply a character string and the value property is represented by an Any class. The following code sample is an example of the NamedValue class.
There is no constructor for this class. The ORB.create_named_value method is used to obtain a reference to a NamedValue object.
package org.omg.CORBA;
public abstract class NamedValue {
public java.lang.String name();
public org.omg.CORBA.Any value();
public int flags();
}
The following table describes the methods in the NamedValue class.
Returns a pointer to an Any object representing the item's value that you can then use to initialize the value. For more information, see “Passing type safely with the Any class”.
Passing type safely with the Any class
This class is used to hold an IDL-specified type so that it may be passed in a type-safe manner.
Objects of this class have a reference to a TypeCode that defines the contained object's type and a reference to the contained object. Methods are provided to construct, copy, and release an object as well as initialize and query the object's value and type. In addition, streaming operators methods are provided to read and write the object from and to a stream. The following code sample is an example.
package org.omg.CORBA;
public abstract class Any {
public abstract TypeCode type();
public abstract void type(TypeCode type);
public abstract void read_value(InputStream input, TypeCode type);
public abstract void write_value(OutputStream output);
public abstract boolean equal(Any rhs);
...
}
Representing argument or attribute types with the TypeCode class
This class is used by the Interface Repository and the IDL compiler to represent the type of arguments or attributes. TypeCode objects are also used in a Request object to specify an argument's type, in conjunction with the Any class.
TypeCode objects have a kind and parameter list property, represented by one of the values defined by the TCKind class.
Note
There is no constructor for this class. Use the ORB.get_primitive_tc method or one of the ORB.create_*_tc methods to create a TypeCode object.
The following table shows the kinds and parameters for the TypeCode objects.
repository_id, interface_name
repository_id, alias_name, TypeCode
length, TypeCode
repository_id, enum-name, enum-id1, enum-id2, ... enum-idn
repository_id, exception_name, StructMembers
digits, scale
id, name
repository_id, interface_id
TypeCode, maxlen
repository_id, struct-name, {member1, TypeCode1}, {membern, TypeCoden}
repository_id, union-name, switch TypeCode,{label-value1, member-name1, TypeCode1}, {labell-valuen, member-namen, TypeCoden}
repository_id, value_name, boxType
repository_id, value_name, typeModifier, concreteBase, members
TypeCode class:
public abstract class TypeCode extends java.lang.Object
implements org.omg.CORBA.portable.IDLEntity {
public abstract boolean
equal(org.omg.CORBA.TypeCode tc);
public boolean equivalent(org.omg.CORBA.TypeCode tc);
public abstract org.omg.CORBA.TCKind kind();
public TypeCode get_compact_typecode()
public abstract java.lang.String id()
throws org.omg.CORBA.TypeCodePackage.BadKind;
public abstract java.lang.String name()
throws org.omg.CORBA.TypeCodePackage.BadKind;
public abstract int member_count()
throws org.omg.CORBA.TypeCodePackage.BadKind;
public abstract java.lang.String member_name(int index)
throws org.omg.CORBA.TypeCodePackage.BadKind,
org.omg.CORBA.TypeCodePackage.Bounds;
public abstract org.omg.CORBA.TypeCode member_type(int index)
throws org.omg.CORBA.TypeCodePackage.BadKind,
org.omg.CORBA.TypeCodePackage.Bounds;
public abstract org.omg.CORBA.Any member_label(int index)
throws org.omg.CORBA.TypeCodePackage.BadKind,
org.omg.CORBA.TypeCodePackage.Bounds;
public abstract org.omg.CORBA.TypeCode discriminator_type()
throws org.omg.CORBA.TypeCodePackage.BadKind;
public abstract int default_index()
throws org.omg.CORBA.TypeCodePackage.BadKind;
public abstract int length()
throws org.omg.CORBA.TypeCodePackage.BadKind;
public abstract org.omg.CORBA.TypeCode content_type()
throws org.omg.CORBA.TypeCodePackage.BadKind;
public short fixed_digits()
throws org.omg.CORBA.TypeCodePackage.BadKind;
public short fixed_scale()
throws org.omg.CORBA.TypeCodePackage.BadKind;
public short member_visibility(int index)
throws org.omg.CORBA.TypeCodePackage.BadKind,
org.omg.CORBA.Bounds;
public short type_modifier()
throws org.omg.CORBA.TypeCodePackage.BadKind;
public TypeCode concrete_base_type()
throws org.omg.CORBA.TypeCodePackage.BadKind;
}
Sending DII requests and receiving results
The Request class, as discussed in “Creating and initializing a request”, provides several methods for sending a request once it has been properly initialized.
Invoking a request
The simplest way to send a request is to call its invoke method, which sends the request and waits for a response before returning to your client program. The return_value method returns a reference to an Any object that represents the return value. The following code sample shows how to send a request with invoke.
try {
...
// Create request that will be sent to the account object
request = account._request("balance");
// Set the result type
request.set_return_type(orb.get_primitive_tc
(org.omg.CORBA.TCKind.tk_float));
// Execute the request to the account object
request.invoke();
// Get the return balance
float balance;
org.omg.CORBA.Any balance_result = request.return_value();
balance = balance_result.extract_float();
// Print out the balance
System.out.println("The balance in " + name + "'s account is $" +
balance);
} catch(Exception e) {
e.printStackTrace();
}
Sending a deferred DII request with the send_deferred method
A non-blocking method, send_deferred, is also provided for sending operation requests. It allows your client to send the request and then use the poll_response method to determine when the response is available. The get_response method blocks until a response is received. The following codes show how these methods are used. The following sample shows you how to use the send_deferred and poll_response methods to send a deferred DII request.
try {
...
// Create request that will be sent to the manager object
org.omg.CORBA.Request request = manager._request("open");
// Create argument to request
org.omg.CORBA.Any customer = orb.create_any();
customer.insert_string(name);
org.omg.CORBA.NVList arguments = request.arguments();
arguments.add_value("name" , customer, org.omg.CORBA.ARG_IN.value);
// Set result type
request.set_return_type(orb.get_primitive_tc
(org.omg.CORBA.TCKind.tk_objref));
// Creation of a new account can take some time
// Execute the deferred request to the manager object-plist
request.send_deferred();
Thread.currentThread().sleep(1000);
while (!request.poll_response()) {
System.out.println(" Waiting for response...");
Thread.currentThread().sleep(1000); // Wait one second between polls
}
request.get_response();
// Get the return value
org.omg.CORBA.Object account;
org.omg.CORBA.Any open_result = request.return_value();
account = open_result.extract_Object();
...
} catch(Exception e) {
e.printStackTrace();
}
Sending an asynchronous DII request with the send_oneway method
The send_oneway method can be used to send an asynchronous request. Oneway requests do not involve a response being returned to the client from the object implementation.
Sending multiple requests
A sequence of DII Request objects can be created using array of Request objects. A sequence of requests can be sent using the VisiBroker ORB methods send_multiple_requests_oneway or send_multiple_requests_deferred. If the sequence of requests is sent as oneway requests, no response is expected from the server to any of the requests.
Receiving multiple requests
When a sequence of requests is sent using send_multiple_requests_deferred, the poll_next_response and get_next_response methods are used to receive the response the server sends for each request.
The VisiBroker ORB method poll_next_response can be used to determine if a response has been received from the server. This method returns true if there is at least one response available. This method returns false if there are no responses available.
The VisiBroker ORB method get_next_response can be used to receive a response. If no response is available, this method will block until a response is received. If you do not wish your client program to block, use the poll_next_response method to first determine when a response is available and then use the get_next_response method to receive the result. The following code sample shows an example of receiving multiple requests.
VisiBroker ORB methods for sending multiple requests and receiving the results:
package org.omg.CORBA;
public abstract class ORB {
public abstract org.omg.CORBA.Environment create_environment();
public abstract void send_multiple_requests_oneway(org.omg.CORBA.Request[] reqs);
public abstract void send_multiple_requests_deferred(org.omg.CORBA.Request[] reqs);
public abstract boolean poll_next_response();
public abstract org.omg.CORBA.Request get_next_response();
...
}
Using the interface repository with the DII
One source of the information needed to populate a DII Request object is an interface repository (IR) (see “Using Interface Repositories”). The following example uses an interface repository to get obtain the parameters of an operation. Note that the example, atypical of real DII applications, has built-in knowledge of a remote object's type (Account) and the name of one of its methods (balance). An actual DII application would get that information from an outside source, for example, a user.
Binds to any Account object.
Looks up the Account's balance method in the IR and builds an operation list from the IR OperationDef.
Creates argument and result components and passes these to the _create_request method. Note that the balance method does not return an exception.
Invokes the Request, extracts and prints the result.