Section 13.2. OBJECT INFRASTRUCTURE FRAMEWORK


13.2. OBJECT INFRASTRUCTURE FRAMEWORK

We have illustrated these ideas by defining an architecture (the Object Infrastructure Framework or OIF), instantiating that architecture for a particular environment (CORBA®/Java™), and creating several validating applications within that framework [2, 8].

Current technology for building distributed, component-based applications uses sockets, messages, remote procedure calls such as DCE™, or Object Request Brokers (ORBs, such as CORBA, JavaRMI™, and DCOM). Without too much loss of generality, we focus on ORB frameworks and use CORBA as our exemplar. CORBA implements distribution by building proxy objects on both the client (caller environment) and server (called environment) to represent a particular server object. The client-side proxy (or stub) is responsible for marshaling a client request into a form that can be transmitted over the network; the server-side proxy (or skeleton) demarshals the request into native data structures for the server to process. ORB technology provides object location transparency and hides the details of marshaling and communication protocols. What it doesn't do is handle ility concerns like partial failures, security, and quality of service. ORBs such as CORBA and Enterprise JavaBeans™ provide different discrete mechanisms for particular ility issues, but such mechanisms typically provide only a finite number of choices for the application architect, and require a good understanding and diligent application of the mechanism by the application programmer.

13.2.1. Injectors

OIF's key implementation idea is to modify ORB proxies so that: (1) each stores a map from proxy methods to a sequence of injectors, and (2) in the proxy processing for a given method, that sequence is invoked between the application and marshaling. The action method of the injector gets an object representing the request. It can interrogate and modify that object for the request's target, method name, arguments, and annotations. Being code, it can perform arbitrary other operations, such as invoking methods on other (remote) objects and changing its local static state. Figure 13-2 illustrates CORBA proxies extended with injectors.

Figure 13-2. OIF inserts injectors between the application and the network.


Injector processing is in "continuation style," meaning injectors invoke the rest of the injector sequence between their "before" and "after" behaviors. (With the continuation pattern, one of the parameters of a routine is a representation of "the rest of the work to be done" after this routine has finished. In OIF, the continuation is represented as a list of injectors, and invoking the continuation is simply calling the first injector in this list, providing it the rest of the list as its continuation.) This has the advantages of allowing the injector stack to naturally catch exceptions, and permitting an injector to forgo or transform the continuation sequence. Our authentication injector illustrates the former advantage. A server-side authentication injector dissatisfied with a request's credentials raises an exception that a client-side injector catches. The client side injector interrogates the user and reinvokes the request with the additional annotation.

Similarly, when methods return static values and do not have side effects, an injector can cache values returned from previous calls. When a request is already in the cache, the caching injector can omit the remote call and return the local value. This has the effect of doing "objects by value" for selected parts of a remote object.

13.2.2. Annotations

Annotations provide a language for applications and injectors to communicate regarding requests. That is, they are a meta-language for statements about requests and the processing state. Annotations can express notions like "This request is to be done at high priority," "Here are the user's credentials," and "Here is the cyberwallet to pay for this request." Annotations can be associated with both requests (request annotations) and processing threads (thread contexts).

OIF annotations are name-value pairs. The names are strings and the values are CORBA ANY types, allowing object references as annotation values. This requires annotation readers and writers to have an implicit agreement about annotation types. Object references in annotations are used for patterns such as continuations ("Send the results of this computation to X") and agencies ("Y can verify my identity"), but not strings; encoding object references as strings would burden the recipient with demarshaling. The framework defines certain common annotations, including session identification, request priority, sending and due dates, version and configuration, cyber wallet, public key, sender identity, and conversational thread. Programs can rely on the common meanings of these annotations. Applications and injectors can create other annotations. Annotations can be implemented as hash tables or property lists. OIF proxies marshal and demarshal request annotations like ordinary procedure arguments.

Requiring injectors to declare the annotations they read and write, and enforcing those declarations, can improve security. We may feel safer using an injector that is restricted to only read the due dates of messages rather than one that can alter user identification or method arguments.

Thread contexts, (annotations associated with processing threads), allow applications to communicate with injectors. On each call, the framework copies the client thread's annotations to the annotations of the nascent request. On the server side, the framework builds the context of the service thread from the request's annotations. On return, the framework copies the server's context to the annotations of the response and then back to update the context of the original client. This scheme has the feature of propagating context through a chain of calls: Client A's call of B at priority x becomes B's context's priority of x. B's request of C (in furtherance of A's call) goes out with priority x. Figure 13-3 illustrates this pattern.

Figure 13-3. Propagating annotations. When object A makes a call on method m in Object B, its thread context is copied over into the annotation of the request. After creating the thread to serve A's request, the annotations of the request are copied to that thread's context. B calls method n on C. This process is repeated for B's calls when handling that request. Thus, an annotation (e.g., priority) set in A is carried over through B to C.


Thread contexts have the advantage of permitting client/injector communication without modifying the application interfaces. They have two disadvantages: newly spawned threads need to copy or share the context of their parents, and there is no primitive linguistic mechanism for neatly "block structuring" a change to a thread's contextallowing, for example, a thread to simply timeshare among tasks.

Declarations can control annotation copying. For example, the number of times the (client) retry-on-failure injector retries is not sent downstream. Similarly, we do not want a server to be able to update a client's user identification. The default behavior is to copy, enabling creating new annotations without modifying existing application code.

13.2.3. Pragma

Our high-level goal is to take functional code, ility specifications, and reusable ility service implementations and weave them together into the actual system code. Ideally, we would like to be able to press the "application: be secure," key, and, lo and behold, the application code is pervasively modified as necessary. That said, we have the sad task of reminding the reader of the dearth of magic in the world. Ilities must be implemented by invoking actual services. Saying you want security does not create security. Rather, you have to define security, as, for example encrypting all communications using { 64 | 128 | 7 } bit { DES | RSA | ROT-13 }, checking the user's { password | fingerprints | DNA } for { every | occasional } access to { all | sensitive } methods, recognizing intrusions { from strange sites | trying a series of passwords | asking too many questions }, keeping track of privileges by { proximity | job function | dynamic agreements }, and so forth. We need the ility architect both to have implementations of the appropriate algorithms (injectors that actually do that work), and to specify where each set of injectors is applied.

Pragma posits a two-level structure to achieve these goals. The architect defines:

  1. A number of ilities (symbolic names like "reliability").

  2. Methods (actions), to achieve each ility. For example, the ility "security" might have an action "high security" that authenticates through fingerprints and includes extensive monitoring and intrusion detection, while the action "low security" might require only passwords and limited monitoring.

  3. A map from the actions to locations in the program. A location can be on a particular method in the implementations of a particular interface, on all the methods of a particular interface, on all the methods of a given name, or everywhere. These definitions can also include assertions about injector ordering.

Pragma also includes constructs for declaring annotations (including their type, default values, and copying context) and for constraining the use of injectors. We support the latter in two ways: an assertion mechanism allows an injector to preclude or demand another, and a cascade mechanism allows the successive refinement of policies within an organization. More specifically, a policy (collection of Pragma statements) may import other policies. A policy may also specify a set of alternatives for an ility. Policies that import such restrictions can choose among (or further restrict) this set, but may not offer new choices. Thus, an enterprise architect may define three acceptable security alternatives, an application suite architect may restrict these to two, and the ility architect for a particular program may choose to use only one.

The Pragma compiler takes as input both a policy and the application IDL™, and generates injector initializations and annotation declarations. Pragma, for each method, interface, and ility, finds the "most specific" way of doing that ility on that method of that interface. (Subinterfaces are more specific than superinterfaces; method-mentions more specific than not, and, arbitrarily, method-mentions more specific than interfaces.) It then orders the actions on that <interface, method> pair and outputs the results as data to the initialization mechanism. Pragma flags as errors combinations that violate constraints. Figure 13-4 shows the Pragma for Vendoom [8], a demonstration system developed using OIF.

Figure 13-4. Pragma for Vendoom.




Aspect-Oriented Software Development
Aspect-Oriented Software Development with Use Cases
ISBN: 0321268881
EAN: 2147483647
Year: 2003
Pages: 307

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net