Implementing Run-time Support for Components1 using Object-Oriented Programming Eddy Truyen, Bo N. Jørgensen, Wouter Joosen. Dept. Computer Science. Catholic University of Leuven – Belgium Maersk Institute for Production Technology, Southern University of Denmark - Denmark
[email protected] Introduction The goal of our research is about how Object Request Brokers (ORBs) can be dynamically reconfigured or customized to evolving application-specific requirements at run-time. To tackle this problem, we are inspired by the component-oriented paradigm. As such, we are building an Object Request Broker as a graph of interconnected components. This graph can be adapted or extended at run-time. However, in order to support such run-time adaptability we need sufficient run-time support for components. This position paper describes how we implemented such run-time support for Java Bean components. We now give an overview of this position paper. In the next section we introduce our research on building reconfigurable ORBs and describe how we use component-oriented programming; we relate our notion of ORB component to the general definition of component and distill which are the requirements we need to achieve our goals. Then in the next section we describe how we implemented a small prototype in Java that realizes support for those requirements. Finally, we conclude. We believe the findings of this paper can be applied in various business domains – and are not only limited to the domain of Object Request Brokers in particular. In this way, we hope to contribute to component engineering issues, one of the suggested topics for this workshop. Requirements for Components in Broker Architectures An Object Request Broker (ORB) defines a distributed object model for developing distributed applications. An important property of Object Request Brokers is that they offer location transparent remote method invocation. Location transparency makes it possible that objects resident at different network nodes can invoke methods on each other, without having to know the exact location of each other in the network. The basic mechanism that enforces location transparency is the Proxy Design Pattern[3]. A client object uses a proxy or stub that serializes the remote method invocations and sends them out onto the network. Conventional Object Request Brokers like CORBA ORBs, Java Sun’s RMI, Microsoft’s DCOM are however not flexible enough to support the evolving next-generation application requirements, with respect to the internal implementation of the Object Request Broker. Nextgeneration ORBs must allow themselves to be dynamically reconfigured or customized to evolving application-specific requirements at run-time. To tackle this problem, we are inspired by the component-oriented paradigm. As such, we are building an Object Request Broker as a graph of interconnected ORB components. This graph can be adapted or extended at run-time. In the context of ORB design, a component is a generic basic building block that implements a reusable part of the ORB functionality. Some examples of components are
1
This research is financially supported by a grant provided by the Flemish Institte for the advancement of scientific-technological research in the industry (IWT)
• • • •
RequestHandler:
implements the appropriate invocation semantics (oneway, synchronous, future…) for remote invocations. EventDemultiplexer: demultiplexes incoming invocation messages to the appropriate RequestHandler component. InvocationScheduler: schedules invocation messages for method execution. Channel: manages network sessions to one specific remote access point using one specific transport protocol. (For example, in TCP/IP an access point is represented by a host and port)
In general, a component is a unit of composition with contractually specified interfaces [1]. We like to tag the interfaces of a component as ComponentInterface to differentiate them from (Java) interfaces in general. Obviously, a component must be able to support multiple component interfaces of different types. Example 1. For example the Channel component supports two different component interfaces (see figure) • An AccessPoint accepts incoming messages from the network and forwards these messages to the registered MessageReceivedListener components • A BindingService sends outgoing messages out onto the network. component interfaces of the Channel component ComponentInterface
AccessPoint Host getHost() void addMessageReceivedListener() void removeMessageReceivedListener()
BindingService Host getHost() void sendMessage()
In the remainder of this section, we give an overview of the requirements for components that we need to serve our purposes. Self-Descriptive and Self-Contained Components In general a component can be implemented in various ways without breaking its contract. Example 2. For example FIFO, Rate Monotonic and Earliest Deadline First are three different strategies for implementing invocation scheduling. Which implementation strategy is most suited cannot predetermined but depends on the timeliness requirements of the application. Each alternative is therefor implemented as a different InvocationScheduler component that supports the same set of component interfaces, contractually specified. Selecting between the alternatives is then a matter of selecting between components. Building an application-specific customized ORB is then supported by a component framework that allows a selective integration of those components that are best suited for the specific application. However, in order to make a good decision, when choosing between the components, knowledge about the different components is necessary. This requires that components are selfdescriptive. The Java Bean introspection mechanism is a powerful tool for learning about properties, events and methods a Java Bean class supports.
2
Selective integration also requires that components are a basic unit of composition. Therefore, components must be self-contained. The Java Bean specification is totally non-constraining the definition of a component; according to the specification any Java class is potentially a bean. A bean can also be a set of classes, and those classes may also be beans in their turn. This makes it quiet difficult to use complex beans as a basic unit of composition. Although this problem was solved at the binary level by packaging beans into Jar files, at the software composition level, the problem is not really solved. Run-Time Support An ORB must also be capable to adapt to evolving circumstances during run-time. Consider the following example with the Channel component to illustrate what we mean with run-time adaptability. Example 3. Consider an ORB that runs on multiple transport protocols. A client object obtains a stub to a remote object. A stub is a local reference to a remote object. Suppose, this remote object uses a transport protocol that has not yet been installed at the client object’s host. Then actions must be undertaken at run-time to select the appropriate Channel component (i.e. one that sets up sessions for that specific transport protocol), then instantiate it at the client-side and initialize it appropriately with the correct remote access point information. Finally (but less importantly in the context of this position paper) the ORB must be reconfigured, such that outgoing invocation messages created by the new stub object, are sent using sessions created by the new Channel component. In order to support such run-time adaptability, we need tools for searching for the appropriate component implementations and mechanisms for instantiating components and referring to these component instances. First of all, this requires a good representation of component types and component instances. It is unfortunately that the clear distinction between class and object in Java is not carried through in Java Beans. Although a bean really is a component, its customized and connected instances are also called beans. Doing Less With More. We now describe the mechanisms, abstractions and classes for supporting the requirements, identified in the previous section. We are using this font, to indicate programming terms and names of classes, implemented in the prototype. Interface Descriptors Identify and describe the component interfaces of the component. For example the Channel component implements the component interfaces AccessPoint and BindingService. Component interfaces are then described by separate InterfaceDescriptor objects. InterfaceDescriptor objects enable to search for a component by interface. In the current prototype a new InterfaceDescriptor object is created by invoking the simple constructor InterfaceDescriptor (Class interface). Abstractions That Guide Component Implementation We implement components by trying to be first of all good designers and secondly by following the Java Beans “design pattern” conventions. In this way we can benefit from the powerful introspection mechanism of Java Beans. [2] Identifies three technical features that need to be supported by component-oriented programming: component containment, multiple instances of component interfaces, and interface dispatch. We implemented ORB components with these three features in mind. This led to the design of a simple white-box framework by identifying common features, like method dispatching and containment invariants. These features were generalized into generic classes such as a hierarchy 3
of Dispatcher classes (similar to vtables of COM), leading to the development of a white-box framework. This white-box framework is specialized when implementing a new component. It is also important that a component controls its own concurrency. This makes collaboration between the various clients of the component obsolete for achieving synchronized access to the component Finally, the component implementation is stored into a Jar file. ComponentType A ComponentType object is the first class representation of a component. It is identified by a unique name. A component type has two important properties. First, it is self-descriptive. It describes which component interfaces are supported by the component (this is a list of InterfaceDescriptor objects). Further, a component type may define a set of metadata descriptors that expose crucial implementation issues of the component. For example, a Channel component type defines metadata that describes to which extent specific values of transmission delay, bandwidth and jitter can be guaranteed by it. This metadata can be used for selecting a component type that is most appropriate for a certain application or under a certain circumstance at run-time. Finally, further descriptors are supported by the Java Bean introspection mechanism. Second, a component type is self-contained. This means that the component implementation and factories (see next section) for instantiating a component are encapsulated within the ComponentType object. As mentioned above, concurrency control and method dispatching are also implemented within the component. These two properties make a ComponentType object a basic unit of composition that is independent deployable. In other words, this object is the component. Component Instantiation In object-oriented programming, object instantiation is done by using the new operator. The new operator creates a new object in the heap and returns a reference to this newly created object (object reference). The class of the object is indicated by a constructor method. A constructor has the same name as its class and takes an implicit this argument (a reference to the newly created object) and a list of initialization arguments. The body of the constructor initializes the instance variables of the newly created object. When we draw the line and step over to component-oriented programming, we need similar mechanisms. However, there are two significant differences: First, instantiating a component, requires a mechanism that returns a set of references to the exported component interfaces (interface references), instead of a single object reference. To achieve this, we defined a class Component. A Component object belongs to one unique component type (as an object belongs to a class). This is implemented by simple aggregation: a Component object aggregates a reference to a ComponentType instance. Further it contains an initialization value and more importantly a set of interface references, that are initially all null. A Component instance is created by invoking the simple constructor Component(ComponentType componentType, java.io.Serializable initializationValue). Class Component defines the method instantiate() for instantiating the component. This method delegates instantiation of the component to an appropriate ComponentFactory object of the component type. A component factory instantiates the component appropriately. It takes the initialization value as argument, creates the necessary objects, initializes them appropriately, and links them with the supported component interfaces by creating the appropriate Dispatcher objects. Finally it updates the set of interface references with non-null references that point to the newly created Dispatcher objects. Thus, the visible effect of invoking the method instantiate() on the Component object is that the set of interfaces references have become non-null references that point to the component interfaces. In other words, after this method has
4
been invoked, a Component object is promoted to a first-class representation of a component reference. It is the mechanism by which other components refer to this component. An important property of Component objects is that they are serializable. This means that they can be passed by copy over the network to a remote peer or stored on stable storage. The following example illustrates the advantages of using the Component class. Example 4. Consider the problem exemplified in example 3. (An ORB runs on multiple transport protocols. A client object obtains a stub object to a remote object. Suppose, this remote object uses a transport protocol that has not yet been instantiated at the client object’s host). Using the Component class, this problem can be handled as follows: The client object extracted the stub object from a ProxyCreatorMessage object that was sent to the client-side ORB. A prototype [3] of this ProxyCreatorMessage object was created and stored when the remote object was created. At that same moment, the Channel component used for accessing this remote object was also added to this prototype ProxyCreatorMessage object in a serialized form (=component type and the initialization value encoding the host and port the remote object listens to). When the ProxyCreatorMessage object is deserialized at the client-side ORB, we just invoke the instantiate() method on the Component object, extracted from the ProxyCreatorMessage object. The effect is that the Component object is promoted to a first-class reference that points to the newly instantiated Channel component. As such another component of the ORB (i.e. the component that manages channels) can refer to it. Second, while object initialization is concerned with initializing instance variables of an object, component instantiation is concerned with interconnecting objects into a web of objects. The optimal layout structure of this web variates depending on the context in which the component is instantiated. Example 5. For example, when instantiating a Channel component at the client-side ORB for connecting to a peer remote object, a client object only (indirectly) uses the BindingService interface of the Channel component. Therefore it is not necessary to create the objects that implement the AccessPoint interface. These objects would just consume heap memory without reason. In the domain of embedded systems this kind of space-efficiency is very useful. This kind of variability is supported by defining a hierarchy of different ComponentFactory singletons as part of the component types. Conclusion We are using component-oriented techniques for building object request brokers, that can be reconfigured dynamically to take application requirements into account. More specifically, we have implemented a prototype of a reconfigurable ORB using the component model of Java Beans. We identified a list of requirements for components that we need to realize this run-time adaptability. We presented the mechanisms and abstractions for supporting these requirements. References Clemens Szyperski. Component Software – Beyond Object-Oriented Programming. [1] Addison-Wesley, Harlow, Essex, 1998. [2] James Noble. Three Features for Component Frameworks. In W. Weck, J. Bosch, and C. Szyperski, editors, Fourth International Workshop on Component-Oriented Programming. http://www.abo.fi/~Wolfgang.Weck/WCOP/99/Accepted.html [3] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns, Elements of Reusable Object-Oriented Software. Addison-Wesley. 1994. Acknowledgements Thanks to Rusty Morris for reading the text of this position paper accurately.
5