|
Sample Chapter
|
|
Getting to Know
Java IDL is a reference implementation in Java of an Object Request Broker (ORB) that is compliant with the OMG-approved Java mapping for IDL. It is designed to provide interoperable CORBA support, including ubiquitous thin client Web access to other IIOP-compliant Common Object Request Broker Architecture (CORBA) services, for Java applications. Java IDL is designed to work with the Web model of zero-install clients that are downloaded from the network into a browser. Because the runtime is implemented completely in Java, it can be packaged and downloaded with any applets that depend on it onto any platform that supports Java. These downloaded applets can then communicate directly with IIOP-compliant enterprise services, thus providing ubiquitous access at low cost. In some cases, the runtime support may already be integrated directly into the Java platform. Although Java IDL was specifically designed to support the downloaded applet model, it works equally well for applicationsóJava clients and servers running stand-alone. This book describes an Early Access release of Java IDL that supports the new OMG-standardized Java language mapping for IDL. For the latest information about Java IDL, see http://java.sun.com/products/jdk/idl/, or look for information about the Java Enterprise API on the JavaSoft Web site. This chapter begins with a discussion of the newly standardized Java language mapping for IDL, including the Java IDL infrastructure. We then describe the application bootstrapping process and the namespace structure that supports it. Some of the main components of the runtime and transport are described with the help of some representative parts of their API. We briefly describe the application-level API and give some excerpts of client and server initialization code. We then discuss some issues specific to Java, and in particular to its downloaded applet model, and describe some mechanisms used to work with or around them. The Java Language Mapping and API Java IDL is the OMG standards-based distributed object framework from JavaSoft. Like the OMG CORBA standard on which it is based, Java IDLís design center focuses on language and environment independence. CORBAís support for multilanguage interoperability is based on its use of IDL, the OMG Interface Definition Language, to unambiguously define the interfaces of exported object services in a form independent of any particular programming language. A consequence of this approach is that in order for a new programming language to be added to the CORBA infrastructure, it is first necessary to define a detailed language mapping between that language and IDL. Such a mapping must enable an unambiguous translation of any valid IDL specification into that native programming language, and it must also provide a compliant mapping for that language of all basic CORBA APIs. The Java language mapping (see Appendix A) used in Java IDL has been standardized by the OMG. The submission was a joint effort of Sun Microsystems and several other ORB vendors. Because Javaís portability and the flexibility of its downloaded applet model create requirements for the portability of object implementations as well as interoperability between objects implemented using ORBs from various vendors, the Java language mapping for IDL goes beyond existing IDL language mappings in the scope of its specification. The full specification is available from the OMG at www.omg.org. Java IDL consists of several major parts: a Java version of the standard CORBA API, a portable stub API, an ORB runtime that supports the Internet Inter-ORB Protocol, an idltojava compiler, and an object Naming service. Java IDL requires JDK 1.0.2 or better. The CORBA API Java IDL implements the standard Java version of the CORBA API from the OMG. This API is described in the CORBA specification in a language-neutral way using IDL. This IDL is written using OMG stylistic conventions that are substantially different from typical Java programming conventions. In particular, object method names are written using underscores rather than mixed case; the low-level runtime CORBA exceptions have class names that are all uppercase; application-level exceptions, known as UserExceptions, do not have class names that end in Exception; and, because much of the API is described in IDL, individual API elements often map to multiple Java interfaces or classes. The end result can be a somewhat jarring experience for a Java developer new to CORBA. Donít be intimidated by the large number of classes that are included with Java IDL. Most of them support dynamic typing and interface reflection. The actual number of CORBA classes used by a developer in a typical application will be quite small. Many applications will use only the ORB class, application-specific CORBA objects described in IDL, and the object Naming service described later in this chapter. Because the Java version of the CORBA API is an implementation of an evolving framework defined by a large standards organization, the OMG, the API is packaged under the org.omg namespace. The core CORBA API is defined in an IDL module named CORBA. The Java version of this API is therefore in the org.omg.CORBA package. The same holds true for other parts of the Java IDL API. The IDL specification for the object Naming service defined in the CosNaming IDL module, for example, is contained under the org.omg.CosNaming Java package. The org.omg.CORBA package consists of the following major groups of classes and interfaces: ORB, Object, exceptions, primitive holders, dynamic typing, Dynamic Invocation Interface (DII), and interface reflection. The ORB class represents an Object Request Broker. All CORBA objects created in Java are associated with an ORB instance. Once created and initialized, an ORB instance can be used to obtain initial CORBA object references and can be used to implement CORBA objects in Java by associating suitable Java objects with the ORB. Additional object references can be obtained as return or out values from calls on CORBA objects, and they will be associated with the ORB of the object being called. Every Java CORBA object implements the org.omg.CORBA.Object interface. The Object interface defines several methods common to all Java CORBA objects, much like the way in which all Java objects inherit from java.lang.Object. Having an object implement the CORBA Object interface, however, is not enough to make it a full CORBA object. For instance, if you implement an object and donít register it with an ORB, then any calls on the methods in org.omg.CORBA.Object may fail and result in a BAD_OPERATION exception being thrown. CORBA defines a set of low-level runtime exceptions that can be thrown as a result of almost any method call on a Java CORBA object. All of these system exceptions inherit from org.omg.CORBA.SystemException, and their names are all uppercase, as defined in the CORBA specification. The SystemException class inherits from java.lang.RuntimeException, and so all Java IDL system exceptions are unchecked Java exceptions. They can be thrown by any CORBA method, even if they arenít explicitly declared in that methodís Java throws clause. Application-level exceptions may be defined in IDL; they are derived from the org.omg.CORBA.UserException class and are normal Java exceptions. Java IDL implements the standard CORBA container class org.omg.CORBA.Any, which can contain any IDL-defined datatype. The actual type contained within an instance of the Any class is described by an org.omg.CORBA .TypeCode object. The TypeCode class provides several methods that give full access to the CORBA type described by a particular TypeCode instance. The TypeCode for each primitive or IDL-defined CORBA data type is available at runtime, along with type-specific methods for manipulating the contents of Any objects. The CORBA Dynamic Invocation Interfaces (DII) allow methods to be called on CORBA objects whose interfaces are discovered at runtime rather than statically at compile time. DII is supported by several classes, including Request, NamedValue, and NVList. This facility allows applications the flexibility to interoperate, at some cost to performance and transparency, with objects whose types cannot be predicted in advance. Many of the classes and interfaces in the org.omg.CORBA package make up the CORBA Interface Repository API. This API allows the full CORBA type information of a CORBA object to be discovered at runtime. CORBA type information consists of the IDL interface inheritance, all IDL operations that the object supports, and the parameters of each operation. In this way, CORBA provides a language-independent network datatype equivalent of the JDK 1.1 java.lang .reflect reflection API. Stubs and Skeletons As with most RPC systems, Java IDLís location transparency is provided by stubs, local proxy objects that implement the same interface as the desired remote object. They are automatically generated from IDL specifications that define the interfaces to the remote services. The stub compiler used by Java IDL is idltojava. Of course, developers program to the Java interfaces generated by the IDL compiler, not to the stub classes. If the Java object that implements an IDL interface is remote, the ORB will automatically create an instance of the appropriate stub class that implements that IDL interface. In addition to stubs, skeleton classes may optionally be generated for use in implementing CORBA server objects. Like stubs, skeletons implement the operation-specific argument marshalling and unmarshalling for a CORBA invocation. Perhaps the single most important example of the Java language mappingís extensions beyond more traditional IDL language mappings is in the area of the stub and skeleton implementations. This was not covered in traditional language mappings because it was assumed that the stubs and skeletons could be considered to be coupled with the specifics of the implementation of a particular vendorís ORB. However, with the downloaded applet model, CORBA applets can be truly portable only if stubs and skeletons can run on any vendorís ORB. This means that stub and skeleton implementations must be able to rely on access to certain APIs that were not formerly included in the language-mapping specifications. The specification of these APIs enables binary compatibility between stubs and skeletons from arbitrary vendors and compliant ORBs. The idltojava compiler takes IDL input and generates Java stubs and skeletons that support the IDL Java language mapping described in Appendix A. For Java IDL, idltojava is available on Solaris, Windows 95, and Windows NT. The Java stubs and skeletons generated by the compiler are then included in the usual Java compilation of the desired Java application (client or server). The Object Request Broker The central interface in the ORB core is the ORB itself. The ORB is the programmer interface to the Object Request Broker. The ORB runtime provides the general infrastructure to support object location and remote connections. Together with the generated stubs and skeletons, it enables the transparent access to heterogeneous remote objects that characterizes the CORBA architecture. Some of the most relevant methods of the ORB API are described in Figure 5.1. Figure 5.1 Excerpts from class org.omg.CORBA.ORB.
import java.util.Properties;
public class ORB {
// Return the ORB singleton object. This method always returns
// the same ORB which is an instance of the class described by
// the org.omg.CORBA.ORBSingletonClass system property. When
// called in an applet environment, the returned ORB can only
// be used as a factory for TypeCodes. The resulting TypeCodes
// can be safely shared between untrusted applets.
public static ORB init();
// ORB constructors for applets and applications
public static ORB init(Applet app, Properties props);
public static ORB init(String args[], Properties props);
// Objects created by the user become ORB objects
// after connect is called.
public abstract void connect(org.omg.CORBA.Object obj);
// To stop an ORB object from receiving remote invocations
// disconnect needs to be called.
public abstract void disconnect(org.omg.CORBA.Object obj);
// Locate references to initial services
public abstract String[] list_initial_services();
// Resolve one of the initial CORBA references by name
public abstract org.omg.CORBA.Object
resolve_initial_references(String object_name)
throws ORBPackage.InvalidName;
// Convert a CORBA object reference to a string
public abstract String object_to_string(org.omg.CORBA.Object obj);
// Restore a stringified CORBA object reference
public abstract org.omg.CORBA.Object string_to_object(String str);
[....]
}
The static ORB initialization methods differentiate between applets and applications. The arguments provide state to the ORB for initialization and later use. The list_initial_services() and resolve_initial_references() methods are used for bootstrapping and are discussed in more detail in the Bootstrapping section. The object_to_string() and string_to_object() methods are used for archival and linearization purposes. They provide the means to convert a CORBA object reference to a string and to later restore it to its original state with all of its type information preserved. Because the format of object references is standardized, this translation works across different CORBA 2.0-compliant ORB implementations. Holders and Helpers Although this chapter does not specifically delve into the details of the Java mapping specification, it is nevertheless useful to describe two categories of support classes that will appear in a number of the examples throughout the remainder of the book. IDL supports three types of parameters: in, out, and inout. As the names indicate, in parameters are transmitted only from client to server, out parameters are transmitted only from server to client, and inout parameters are transmitted in both directions. Because Javaís parameter-passing semantics do not support out or inout parameters directly, the mapping required the introduction of holder classes to support these parameter-passing modes. The underlying system includes predefined holder classes for basic IDL data types, with new ones being generated by idltojava for all user-defined types. A holder includes a public data member that holds its value, and a pair of constructors: one taking no arguments and one taking an initial value.
package org.omg.CORBA;
public class LongHolder {
public long value;
public LongHolder () { }
public LongHolder (long initial) {value = initial;}
}
For all user-defined IDL types, idltojava generates a class of the same name that represents the type as well as a helper class that contains static methods for manipulating the type. In addition to the basic manipulation methods (Any insert and extract, typecode access, etc.) that appear on all helper classes, helper classes for interfaces also include a static narrow() method, used to convert a more general superclass such as Object to the particular type on which the method is invoked. Initializing Java IDL Applications Although the focus of the CORBA specification is interoperability, it does not currently specify the bootstrapping process in sufficient detail to ensure portability at that level. The details of how one connects to an ORB to get the object reference to an initial Naming context are left ambiguous, leading to vendor-specific implementations that do not interoperate. To address this problem, Sun Microsystems has developed a proposal for an Interoperable Naming Service (INS) in cooperation with other ORB vendors. This proposal covers the details of the bootstrapping protocol, as well as some common initial namespace contexts and common Naming conventions. This proposal is still in draft form and has not yet been officially standardized by the OMG, but as several key vendors are planning to implement key components of the proposal it is likely to be useful even prior to formal standardization. Java IDL implements the bootstrapping protocol, and the designated bootstrapping port (900) has been reserved with the Internet Assigned Numbers Authority (IANA). The proposal draft can currently be found at www.sun.com/solaris/neo/wp-naming-svc. COS Naming Perhaps the most important service for a distributed application is Naming. The OMG defines the COS Naming service as one of its Common Object Services, and a transient version of this service is implemented as part of Java IDL. The COS Naming service divides the namespace into a number of contexts. Contexts are roughly comparable to directories (or folders), and much like directories, they may be arbitrarily nested. Names are composed of an ordered series of name components. The last component is the name of the designated object, which is comparable to a file in its position as a leaf node in the Naming hierarchy. The intermediate components are an ordered list of contexts that are used to navigate the namespace. Names may be added to and deleted from the namespace via the bind(), unbind(), and rebind() methods; objects are located via the resolve() method (see Figure 5.2). Access to the COS namespace is generally provided at initialization time, where a reference to an initial Naming context is provided as one of the CORBA Initial Services, as described in the next section. Figure 5.2 Excerpts from org.omg.CosNaming.NamingContext.
package org.omg.CosNaming;
import org.omg.CosNaming.NamingContextPackage.*
public interface NamingContext {
// bind an object reference to a named location in a NamingContext
public abstract void bind(NameComponent n[],
org.omg.CORBA.Object obj)
throws NotFound, CannotProceed, InvalidName, AlreadyBound;
// bind a NamingContext to a named location in another context
public abstract void bind_context(NameComponent n[],
NamingContext nc)
throws NotFound, CannotProceed, InvalidName, AlreadyBound;
// bind a new object reference to an existing named location
public abstract void rebind(NameComponent n[],
org.omg.CORBA.Object obj)
throws NotFound, CannotProceed, InvalidName;
// bind a new context object reference to an existing named location
public abstract void rebind_context(NameComponent n[],
NamingContext nc)
throws NotFound, CannotProceed, InvalidName;
// resolve a named location into the corresponding object reference
public abstract org.omg.CORBA.Object resolve(NameComponent n[])
throws NotFound, CannotProceed, InvalidName;
// unbind (remove) the named object reference from the namespace
public abstract void unbind(NameComponent n[])
throws NotFound, CannotProceed, InvalidName;
}
Bootstrapping In steady state, using CORBA is extremely transparent to the programmer. Clients have already located the remote objects that they wish to interact with and stored them in local variables for easy reference. Remote method invocation simply involves an apparently local method invocation on that object, and the stubs, skeletons, and ORB runtime take care of almost everything else. To reach such a steady state, however, it is necessary to gain access to an initial ORB runtime object that, in turn, needs to be able to locate and connect to the remote ORB runtime objects responsible for the designated remote objects. Bootstrapping is the process of connecting a client to a CORBA object from which it can locate the servers it wants to access. Typically this will be a root COS Naming context running on a designated CORBA host. The CORBA 2.0 specification defines a bootstrapping process based on two methods on the ORB: list_initial_services and resolve_initial_references (see Figure 5.1). list_initial_services() returns an array of Java strings containing the names of the available initial services, which generally include a reference to an initial COS Naming context. resolve_initial_references() takes one of these names and returns a CORBA object reference for the CORBA object it represents. Once the returned object is narrowed to the appropriate type, it is ready to be used by the application. Java IDL implements the bootstrapping API described in the Interoperable Naming Service proposal as an integrated part of the COS Naming server. Eventually, most ORBs will support the bootstrap API by default. The proposal specifies a default port on which the bootstrap server listens (port 900) and a very simple IIOP-based interface for requesting a list of initial object references. Client Setup Clients need add only minimal Java IDL-specific code to their Java programs to access CORBA objects. Figure 5.3 illustrates the necessary steps. Inside the application class, it is first necessary to initialize the ORB, as shown in the example code. Once the ORB is initialized, a client can access the ORB initial services to locate a root Naming Context and use it to locate the desired service (in this case, ìExampleî). The service, once located, is returned as a generic CORBA object reference, which must then be narrowed to the appropriate type (in this case, Example) using the ExampleHelper class. Once this initialization has been completed, the example variable may be used throughout the remainder of the application as a normal Java variable. Method invocations on the object reference held by that variable appear to be local but will, in fact, be converted into method invocations on the appropriate remote object. Of course, in reality, these CORBA objects may not be local so applications should be prepared to catch and recover from any exceptions thrown when access to a remote object fails unexpectedly. Server Support The CORBA architecture includes support for both persistent and transient objects. A transient object exists for no longer than the lifetime of its process. A persistent object exists beyond the lifetime of a particular process, and an object reference for a persistent object is guaranteed to remain valid over time. The server-side ORB maintains persistent information about the server object implementation and its state, although the responsibility for maintaining persistent data remains with the developer. This information allows the ORB to activate such a persistent object server in response to an incoming request even if it happens to be inactive at the time the request arrives. Java IDL currently supports only transient objects. The original CORBA API for persistent objects, known as the BOA (Basic Object Adapter), was not specified in sufficient detail to ensure interoperability. The BOA has now been deprecated by the OMG in favor of a new Portable Object Adapter (POA) API. Java IDL Transient Object API The Java IDL transient object API is the easiest way to implement a CORBA object. The next few subsections describe how to create an object using the transient object API. The Servant Base Class A server developer implements an IDL interface by providing a programming language class called a servant. The servant implements the operations in the IDL interface. A server is a program that implements one or more IDL interfaces; that is, it provides one or more servants. For each IDL interface X, a class named _XImplBase will be generated by idltojava.
public abstract class _XImplBase implements X {
}
_XImplBase is declared to implement the interface X, but, in fact, it does not itself actually provide implementations for Xís methods. The developer must subclass from it and implement all of the methods in an X servant class. The Servant Class For each object implementation, the developer must write a servant class. Instances of the servant class implement potential CORBA objects. Each servant class implements an IDL interface. To implement an interface named X using the Java IDL transient object API, the servant class must extend an _XImplBase class. The servant class must implement the methods corresponding to the operations and attributes of the IDL interface, as defined by the Java mapping specification for IDL interfaces. Providing these methods is sufficient to satisfy all abstract methods defined by _XImplBase. Creating a CORBA Object To use the newly implemented object, just create a new instance of the servant class. Initially, the object is just an ordinary Java object and can be used as such. The easiest way to create a CORBA identity for the new servant instance is to pass the object as a parameter to calls on other CORBA objects. If the object that is called is outside the local Java Virtual Machine (VM), the passed servant instance will automatically be turned into a full CORBA object. To directly associate the servant with an ORB instance and give it a CORBA identity immediately, you can call the connect() method on that ORB (see Figure 5.1). Even after a servant is connected to an ORB and becomes a CORBA object, local calls on the object will continue to go directly to the servant. Only remote calls will be processed by the ORB. To disassociate the CORBA identity of a servant use the disconnect() method on the same ORB to which that object was originally connected. Because a reference to the servant object will become a CORBA object reference automatically if it is ever passed to a remote client, developers can implement objects that are as efficient as regular Java objects when used locally but can be distributed across a network when needed. The Portable Object Adapter The role of an object adapter is to create CORBA object references that correspond to objects that a server wishes to make available to CORBA clients. An object adapter is a server-side API only. When a client makes a request, the adapter notifies the developerís server code that a request has come in for that particular object reference. In theory, there could be multiple object adapters available for use by a server. Adapters could be specialized to different problem domains or oriented towards supporting certain kinds of object data persistence. The scheme described earlier in the chapter for implementing transient CORBA objects in Java is actually a very simplified object adapter built into the Java ORB class. The Portable Object Adapter, or POA, is a new OMG standard API for implementing CORBA objects in any language with an IDL mapping. The POA can be used to implement objects using a wide range of different styles or policies including both transient objects and persistent objects. The POA is a general-purpose adapter that has a flexible set of policies that guide its behavior. It is specified in IDL, which is mapped into a specific language using the normal rules for mapping IDL. In addition, a few features are specified specifically for each language. At the time of writing, the Java mapping of the POA has not been completed and has thus not yet been included in the Java IDL implementation. As a result, the Java code examples in this section may undergo a few minor changes, but most of this section will remain accurate. The complete POA specification is available from the OMG Web site, www.omg.org. The current version is also available as ftp://ftp.omg.org/pub/docs/orbos/97-04-14.pdf. An ORB that supports implementing objects via the POA API will return an instance of org.omg.PortableServer.POA when its resolve_initial_references() method is called with the name ìRootPOA.î The root POA can be used directly to implement CORBA objects or it can be used to create named child POA instances, which are then themselves used to implement objects. The entire POA IDL specification is contained inside a PortableServer IDL module. All standard OMG IDL specifications are defined to be mapped under the org.omg Java package prefix, so the POA code lives under the org.omg.PortableServer package. The following code example shows a subset of methods from the POA interface related to creating new child POA instances.
package org.omg.PortableServer;
public interface POA ...
{
// POA readonly attributes.
POA the_parent();
POAManager the_POAManager();
POA create_POA(String name, POAManager manager,
org.omg.CORBA.Policy[] policies)
throws AdapterAlreadyExists, InvalidPolicy;
}
POA Policies Every POA instance follows a set of policies established when it was created. The root POA has a standard set of policies appropriate for implementing simple transient objects. Developers can create new POA instances that follow different policies. The root POA implements the following policies: ORB_CTRL_MODEL, TRANSIENT, UNIQUE_ID, SYSTEM_ID, RETAIN, USE_ACTIVE_OBJECT_MAP_ONLY, and IMPLICIT_ACTIVATION. This means that a separate thread may be used to handle each incoming request to the servant, that object references are transient and invalid after the server exits, that each servant can implement only one object reference (and therefore one object id), and that the object id is chosen automatically by the POA and kept in an active object map. The following table shows each of the different policy types and the range of policy options.
Policy Type Policy Options Thread ORB_CTRL_MODEL,
SINGLE_THREAD_MODEL Lifespan TRANSIENT, PERSISTENT Object Id Uniqueness
UNIQUE_ID, MULTIPLE_ID Object Id Assignment USER_ID, SYSTEM_ID
Implicit Activation IMPLICIT_ACTIVATION, NO_IMPLICIT_ACTIVATION
Servant Retention RETAIN, NON_RETAIN Request Processing USE_ACTIVE_OBJECT_MAP_ONLY,
Creating a CORBA Object To create a CORBA object with POA you need three items: a POA instance, an interface repository id, and an object id. The interface id determines the most-derived IDL interface that the object represents. The object id is used to determine which object is being called when a request comes into the ORB. Every distinct CORBA object reference created by a particular POA instance has its own unique object id. The developer has total control over each of these. Different styles of object implementation need different kinds of flexibility. For simple transient objects, the root POA can be used and it can determine the interface id from the IDL compiler-generated servant base class. It will also automatically choose the object id because it uses the SYSTEM_ID policy. This means you need only create an instance of the servant and call the POA servant_to_reference() method to create the CORBA object reference. Sometimes it is useful for the developer to assign the object id. For instance, each entry in an existing computerized personal calendar database may already be assigned a unique id by the underlying implementation. To create CORBA object references that represent this existing database, each calendar entry can be its own CORBA object and the existing database id can be used as the object id. A POA object id is just a sequence of octets (in other words, a byte array in Java). The following methods in the POA interface can be used to create new CORBA object identities. The actual behavior of these methods is subject to the policy configuration of the actual POA instance that is used.
package org.omg.PortableServer;
public interface POA ...
{
// Use following 2 methods with RETAIN policy. Obtain object reference
// through servant_to_reference() or id_to_reference() methods
byte[]
activate_object(Servant p_servant)
throws ServantAlreadyActive, WrongPolicy;
void
activate_object_with_id(byte[]id, Servant servant)
throws ServantAlreadyActive, ObjectAlreadyActive, WrongPolicy;
// Requires SYSTEM_ID policy.
org.omg.CORBA.Object
create_reference(String interface_id)
throws WrongPolicy;
org.omg.CORBA.Object
create_reference_with_id(byte[] oid, String interface_id)
throws WrongPolicy;
// Useful with root POA for transient objects
org.omg.CORBA.Object
servant_to_reference(Servant servant)
throws ServantNotActive, WrongPolicy;
}
Servant Managers and the Active Object Map Other than creating new object references, the primary task of the POA is to process incoming requests from clients. To do this it must find the servant that provides the runtime behavior for the target object reference. The way in which it does this is controlled by POA policies. Using typical policies, the POA takes the object id from the target and looks for a servant in its active object map. If the map does not contain a servant for that object id then the POA will check to see if a servant manager has been registered. If so, it calls the servant manager passing it the object id and asking it to return a servant. By implementing and registering a servant manager, a developer can directly control the association of object ids with servants. By choosing to use an active object map, the developer can cause the POA to cache the results of the servant lookup. It is also possible to choose POA policies that use a single default servant for all objects or for any objects not explicitly activated by the server. Persistent Objects Persistent objects are objects whose lifespan is independent of the server that created them. If a client makes a request on a persistent object and its server has exited, an ORB agent will cause the objectís server to be restarted automatically. The newly started server must recreate any POA instances it has used to create its object references. These can be recreated during startup or the server can implement and register an Adapter Activator, which will be called to recreate the POA instances as required for incoming client requests. It is up to each POA implementation to provide administrative tools for configuring ORB agents so they may start persistent servers when needed. The POA Manager Every POA instance is associated with a POA Manager that controls its request processing state. By default, each POA has its own unique manager, but POAs can also be associated at creation time with an existing manager. Newly created managers are in the holding state. The POAs controlled by the manager will begin processing requests when their managerís activate() method is called. POA Managers can also be set to discard incoming requests or can be deactivated as part of server shutdown. Servants and Servant Base Classes Just like the Java IDL transient object API, the POA also specifies a standard servant base class that will be generated for each IDL interface. The POA servant base class contains abstract method declarations for each of the IDL interfaceís methods. The object developer writes a servant class to implement an object. The servant class inherits from the servant base class and provides implementations for each of the IDL operations in the interface. The POA methods shown here support looking up the associations between servants, object ids, and object references for objects implemented with a particular POA instance.
package org.omg.PortableServer;
public interface POA ...
{
// Methods for obtaining servants, references and object ids
byte[]
servant_to_id(Servant p_servant)
throws ServantNotActive, WrongPolicy;
org.omg.CORBA.Object
servant_to_reference(Servant p_servant)
throws ServantNotActive, WrongPolicy;
Servant
reference_to_servant(org.omg.CORBA.Object reference)
throws ObjectNotActive, WrongAdapter, WrongPolicy;
byte[]
reference_to_id(org.omg.CORBA.Object reference)
throws WrongAdapter, WrongPolicy;
Servant
id_to_servant(byte[] oid)
throws ObjectNotActive, WrongPolicy;
org.omg.CORBA.Object
id_to_reference(byte[] oid)
throws ObjectNotActive, WrongPolicy;
}
Java IDL Infrastructure In this section, weíll take a look at how the Java IDL implementation is structured to maintain portability across ORB implementations. The interfaces described in the following subsections are not used by developers, but they help to understand the infrastructure underlying the developer API. The Portable Stub API The portable stub API is part of the OMG Java IDL mapping standard. It is designed to maintain a separation between the classes generated by an IDL compiler and the underlying ORB implementation. This allows ORBs that implement different network protocols, different qualities of service, and other features to be used with the same application and stub and skeleton classes. It also means that applets can be written so that they can be downloaded into any Web browser that contains an embedded ORB implementation, even if that ORB implementation is different from the one for which the applet was originally developed. As long as the ORB in the browser supports the OMG Java IDL mapping standard, your applet can use the bundled ORB without having to download the ORB implementation classes with your applet. You can still download your own ORB implementation and use it instead, if desired. To make this work, the classes generated by the IDL compiler depend on the portable stub API in the org.omg.CORBA.portable Java package to interface with the underlying ORB. This means that an interface that was previously private, the one used by generated stubs and skeletons to access the ORB internals, must now be standardized across different vendorsí ORB implementations. Note that the portability of the Stub API is independent of object implementation details on the server, and thus has no specific relation to the POA or any other object adapter. There are four primary classes defined there: the ObjectImpl class, the Delegate class, and the input and output streaming classes. Note that this API is typically used by generated stubs, not by developers. ObjectImpl Every stub and skeleton generated by an IDL compiler for Java will extend the ObjectImpl class (see Figure 5.4) defined in the org.omg.CORBA.portable package. The ObjectImpl class contains a single instance variable called a delegate. The methods defined by the CORBA Object interface are implemented by forwarding the invocations to the delegate object, which is described in the following section. Figure 5.5 illustrates the inheritance relationship for the stubs of an interface X. Delegate The delegate contains the ORB-specific state and implementation for the object reference. The separation of the type-specific information in the stub and the ORB-specific information in the delegate allows the same delegate implementation to be used for any IDL interface. In addition, this separation also allows the same stub to be used with different ORB implementations. Each ORB implements delegate classes by extending the common Delegate base class (see Figure 5.6). Input and Output Streams The other part of enabling IDL compiler-generated classes to be ORB independent is handling the streaming or marshalling of IDL data types. IDL data types are converted into protocol-specific formats using instances of the InputStream and OutputStream classes (see Figure 5.7). These are abstract classes that each ORB implementation must subclass to implement specific data-encoding formats such as the one used for IIOP. Simple types are marshalled directly while composite types such as IDL structs are marshalled as combinations of the primitive ones. Living Inside Firewalls (HTTP Tunneling) Many companies protect their internal data by surrounding their internal networks with firewalls. In general, firewalls work by requiring that all external communication pass through a single secure gateway. Some firewalls restrict only incoming communication; others restrict outgoing communication to connections initiated by a small set of known programs or protocols, such as browsers, mail programs, ftp, and Telnet programs; others completely restrict both incoming and outgoing communication. There is no workaround for this problem in the first release of Java IDL, although some ORB vendors provide strategies such as HTTP tunneling or special firewall modules. A real fix will require firewall vendors to properly support IIOP. Applet Issues Downloaded applets represent arbitrary active content running on a userís private machine. This provides a great deal of flexibility, but it also enables a number of potential security attacks. Unlike other types of active content such as ActiveX, Java restricts applets to an applet sandbox from which their access to system resources such as disk, memory, and the network is carefully controlled. Prior to the JDK 1.1 release, applets were effectively prevented from most external accesses. They were not allowed access to the local disk or to underlying operating system calls, they were not allowed to listen for incoming connections, and they were prevented from opening outgoing connections to any host other than the one from which they were downloaded. These restrictions are great for security, but they severely restrict the functionality of distributed programs. The JDK 1.1 and later provide support for relaxing these restrictions based on certificates and digital signatures. Digitally Signed Applets The restrictions imposed on applets are based on the assumption that because they are downloaded they are inherently not trusted. As the state of the art in distributed security and authentication improves, this assumption can gradually be relaxed. The JDK 1.1 release supports digitally signed applets. An applet can be tagged with a particular digital signature, or a set of digital signatures, and applet security restrictions can be made conditional based on the acceptance of particular digital signatures. In essence, this is no worse than trusting the software that comes on a particular vendorís CD. Performance Although we have been focusing on functionality rather than performance, performance issues arise directly out of the basic model of the Java Virtual Machine and of applets. One area in which performance has been a problem has been applet download performance. The default HTTP protocol requires a new TCP connection for each HTTP message round-trip, and the default Java class-loading protocol requests a single class file at a time. Because the Java compiler allows only a single Java class per generated class file, and because ìgoodî Java coding technique encourages large numbers of small classes, this combination of features ensures problematic download performance for an applet of any reasonable size. There is hope, however, because most of these conditions are in the process of changing. In the medium term, the HTTP protocol has evolved toward permitting persistent connections. In addition, Java archive files enable the large number of small data transfers involved in current applet downloads to be replaced by a small number of much larger data transfers. This will significantly reduce the download time. JDK 1.1 and Netscape 4.0 already support Java archive files. These are referred to as JAR files and consist of a collection of Java classes (and other data such as image files) in optionally compressed Zip format along with some meta information. A list of archive files may be designated via the keyword ARCHIVE within an HTML Java APPLET tag followed by a comma-separated list of archive filenames. The named archive files will be searched for in the location defined by the already existing applet CODEBASE keyword. Another performance issue has to do with Javaís being an interpreted language. Its portability depends on the fact that the Java source code is compiled into architecture-neutral bytecodes rather than architecture-specific executables. Interpretation provides a great deal of flexibility, but it necessarily adds some performance overhead. One solution to this problem is JustInTime (JIT) compilers. These compilers are built into the Java VM runtime and translate the Java bytecodes into native processor instructions on the fly. For smaller, short-lived applications, the overhead of running a JIT compiler may not be beneficial; however, for larger, longer-lived applications it can significantly improve the overall performance. Chapter Summary Java IDL provides support for Java applications to participate fully in the world of three-tier enterprise distributed systems. Java itself contributes an easy, lightweight entry into Web-based computing with its zero-administration thin client model. Together, these complementary technologies provide businesses with a way to extend their heterogeneous enterprise systems, complete with legacy data, to include widespread client access over the Web. Java IDLís architecture and implementation are compliant with the new OMG Java language mapping, which goes beyond the language mappings of more traditional languages to provide a portable binary interface between applications, their IDL compiler-generated stubs, and ORB implementations.
Now that we have a basic understanding of the Java
IDL architecture and some of the key APIs, the following chapters
will contain examples of clients and servers of increasing complexity
to illustrate the use of these APIs in the construction of distributed
enterprise applications. # Figure 5.3 Java IDL client initialization.
package ExampleModule; // Contains classes specified in the IDL module
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
public class ExampleClient
{
public static void main(String args[])
{
try{
// create and initialize the ORB
ORB orb = ORB.init(args, null);
// get the root naming context
org.omg.CORBA.Object objRef =
orb.resolve_initial_references("NameService");
NamingContext ncRef = NamingContextHelper.narrow(objRef);
// resolve the Object Reference in Naming
NameComponent nc = new NameComponent("Example", "");
NameComponent path[] = {nc};
Example exampleRef = ExampleHelper.narrow(ncRef.resolve(path));
} catch (java.lang.Exception e) {
System.out.println ("Failure resolving object: " + e);
}
// invoke on 'example' as a normal Java object...
[ ... ]
}
}
Figure 5.4 ObjectImpl API.
public abstract class ObjectImpl implements org.omg.CORBA.Object {
private Delegate _delegate;
public Delegate _get_delegate() {
if (_delegate == null)
throw new org.omg.CORBA.BAD_OPERATION();
return _delegate;
}
public org.omg.CORBA.InterfaceDef _get_interface() {
return _get_delegate().get_interface(this);
}
public boolean _is_a(String repository_id) {
return _get_delegate().is_a(this, repository_id);
}
[...]
}
Figure 5.5 Inheritance for object stubs.
Figure 5.6 Delegate API.
public abstract class Delegate {
public abstract org.omg.CORBA.InterfaceDef
get_interface(org.omg.CORBA.Object self);
public abstract boolean
is_a(org.omg.CORBA.Object self, String repository_id);
public abstract boolean
is_equivalent(org.omg.CORBA.Object self, org.omg.CORBA.Object rhs);
[...]
}
Figure 5.7 InputStream and OutputStream API.
public abstract class InputStream {
// simple type unmarshalling
public abstract byte read_octet();
public abstract short read_short();
// array type unmarshalling
public abstract void read_octet_array(byte[] value, int offset,
int length);
[...]
}
public abstract class OutputStream {
// simple type marshalling
public abstract void write_short(int arg);
public abstract void write_string(String arg);
// array type marshalling
public abstract void write_char_array(char[] arg, int offset,
int length);
// object marshalling
public abstract void write_Object(org.omg.CORBA.Object value);
[...]
}
|