Section 4.3. INTRA-OBJECT CROSSCUTTING WITH COMPOSITION FILTERS


4.3. INTRA-OBJECT CROSSCUTTING WITH COMPOSITION FILTERS

In this section, we begin by briefly explaining the CF object model, focusing on concerns that crosscut within an object. We discuss the basic mechanisms of the CF model using the example that was introduced in Section 4.2. This chapter presents a conceptual model of composition filters, explained in an operational way. Actual implementations may vary substantially, as we discuss in Section 4.5.1.

4.3.1. Concern Instance = Object + Filters

The CF model is a modular extension to the conventional object-based model [38] used by programming languages such as Java, C++, and Smalltalk, as well as component models such as .NET, CORBA, and Enterprise JavaBeans. The core concept of this extension is the enhancement of conventional objects by manipulating all sent and received messages. This allows expressing many different behavioral enhancements since in an object-based system all the externally visible behavior of an object is manifest in the messages it sends and receives. Figure 4-4 illustrates this extension by "abstracting" the implementation object with a layer[1] that contains filters for manipulating sent and received messages. These filters are grouped into subcomponents called filter modules. Filter modules are the units of reuse and instantiation of filter behavior. In addition to the specification of filters, the filter modules may provide some execution context for the filters.

[1] This is different from straightforward object wrappinge.g., problems such as object schizophrenia [33] are avoided because in the CF case, there is only one object with a single identity.

Figure 4-4. Simplified representation of concern instances with filters.


The filters define enhancements to the behavior of objects. Each filter specifies a particular inspection and manipulation of messages. Input filters and output filters can manipulate messages that are respectively received and sent by an object. After the composition of filter modules and filters, received messages must pass through the input filters and be sent through the output filters.[2]

[2] More precisely, both filter modules and filters are composed together using dedicated composition operators. In this chapter, we assume that both filter modules and filters within those modules are composed with a fixed order that depends on the declaration order of various elements.

The enhanced object, which we refer to as the implementation object, may be defined in any object-based language, given the availability of proper tool support for that language. The main requirement is that the object offers an interface of available methods. Two types of methods are distinguished: regular methods and condition methods (conditions for short). Regular methods implement the functional behavior of the object. They may be invoked through messages if the filters of the object allow this. Conditions must implement side-effect free Boolean expressions that typically provide information about the state of the object.

Conditions serve three purposes:

  1. They offer an abstraction of the state of the implementation object, allowing filters to consider only relevant states.

  2. They allow filters to remain independent of the implementation details of the implementation object. This has the additional benefit of making the filters more reusable.

  3. Conditions can be reused by multiple filter (modules) and concerns.

In summary, conditions enforce the separation of the state abstraction and the message filtering concerns.

4.3.2. Evolution Step 1: Protecting Documents

In the initial system, each task could access all the attributes of a document. A request dispatcher clerk, for instance, could accidentally edit the medical data properties. To overcome this problem, the second release placed restrictions on the execution of messages. Within a given document-processing phase, only the appropriate tasks should have access to the interface methods of the corresponding document. A possible implementation for this is to test the role of the sender of the received message[3] before executing the corresponding method, throwing an exception if the method was invoked by an inappropriate object.

[3] Depending on the implementation language, it may be non-trivial to identify the sender of a message and its role.

In the evolution steps that follow, we reuse all the functionality that has been implemented so far, incrementally introducing the new requirements through modular extensions.

With conventional objects, there are two primary extension alternatives: subclassing an existing document class or aggregating an instance of an existing document class and reusing its behavior internally by sending ("forwarding") messages to it. Both of these patterns require overriding many methods of the reused class. These methods must implement the verification of the identity of the sender object, in addition to invoking the behavior of the original method. We refer to [12] for an extensive discussion on advantages and disadvantages of inheritance-based and aggregation-based composition.

In the actual pilot project, the number of required method redefinitions was substantially high, due to the quantity of document types and the number of methods for each document type that had to be protected.

4.3.3. A Composition Filters Solution

We describe filter specifications in more detail using the ProtectedClaim Document example described in the previous section. The code for ProtectedClaimDocument is shown in Listing 4-1. Using inheritance, ProtectedClaimDocument extends the existing class ClaimDocument with a protection mechanism to prevent inappropriate objects from invoking certain methods. We refer to this mechanism as multiple views. We first introduce the overall structure of a composition filters specification, show how messages are processed by filters, illustrate how filters can express inheritance, and finally explain how filters can express views in a modular way.

Class ProtectedClaimDocument consists of a filter module, DocumentWithViews (lines 219), and an implementation part, which is defined as a Java class named ProtectedClaimDocumentImpl (lines 2031). In principle, any object-based language can be used to realize the implementation part. In this example, the implementation defines the conditions and methods that are newly introduced by class ProtectedClaimDocument.

Listing 4-1. Implementation of ProtectedClaimDocument with composition filters
  (1)  concern ProtectedClaimDocument begin  (2)  filtermodule DocumentWithViews begin  (3)    internals  (4)      document: ClaimDocument;  (5)    conditions  (6)      Payment; MedChck; ReqHndlr; ReqDisp; Approval;  (7)    methods  (8)      activeTask():String;  (9)      document.*;  //all methods on interface of ClaimDocument (10)    inputfilters (11)      protection: Error = { (12)        Payment=>{setApprovedAmount, getApprovedAmount}, (13)        MedChck=>{setMedStatus, getMedStatus, getClaimMotivation}, (14)        ...//etc. for the other views (15)        True=>{mail, print, status, getClientName, getCategory}}; (16)      inh : Dispatch = { inner.* , document.* }; (17)  end filtermodule DocumentWithViews; (18)  implementation in Java  //for example (19)    class ProtectedClaimDocumentImpl { (20)      boolean Payment() {return this.activeTask()!="Payment" }; (21)      boolean MedChk()     { ... }; (22)      boolean ReqHndlr()   { ... }; (23)      boolean ReqDisp()    { ... }; (24)      boolean Approval()   { ... }; (25)      String  activeTask() { ... }; (26)      } (27)  end implementation (28)  end concern ProtectedClaimDocument; 

The filter module DocumentWithViews contains four parts: The first is the declaration of internals (lines 34); these are internal objects, encapsulated by the filter module. A new instance of an internal is created for each instantiation of the filter module that declares it. In this example, an instance of ClaimDocument named document is created for each instantiation of the filter module. A filter module can also declare externals; these are references to instances created outside the filter module and concern and are used for representing shared state. Typically, the reference consists of a name that is bound according to lexical scoping rules.

The second part refers to the conditions (lines 56). Conditions serve to abstract the state of the implementation object. In this case, five conditions are declared. It is possible to reuse conditions declared elsewhere by using the syntax <instance name>.<condition name>, where <instance name> must be a valid internal or external object. The form <instance name>.* can be used to 'import'[4] all conditions from an instance. Possible name conflicts are resolved by selecting the first occurrence of a condition name based on the order of declaration.

[4] This is semantically close to the notion of inheritance of conditions.

The third part of the filter module refers to the lines 79, where the methods that are used within the filter expressions are declared, including the types of arguments and return values. Line 8 shows the declaration of method activeTask(), which takes no arguments and returns an instance of type String. For the purpose of language independence, the adopted notation follows the UML conventions [31] where appropriate. Line 9 shows the use of a wildcard * as a shorthand notation to declare all the methods in the signature of class ClaimDocument.[5] Again, possible name conflicts are resolved by selecting the first method according to the order of declaration.

[5] This shorthand notation can be seen as a compromise between explicitly declaring all the methods on one hand and convenience and open-endedness on the other hand. A particular property of the wildcard is that extensions to the interface of the reused class (in the current example, ClaimDocument) will be automatically available to the reusing concern (ProtectedClaimDocument).

The fourth and the last part of this example filter module is the specification of the input filters in lines 1017. Two filters are declared in this part; protection and inh. The first filter handles the multiple views, and the second specifies the inheritance relation from the "previous version"; i.e. from ClaimDocument. We will go into more detail about the way filters work in the next subsection.

All the named instances, conditions, and methods within filter specifications must always be declared within the filter module. The resulting declarative completeness (as in Hyper/J [32]) improves the ability to do modular and incremental reasoning about the correctness of the program. In addition, when instantiating a filter module, it is straightforward to verify that all the declared entities within the filter module can actually be bound to concrete implementations.

4.3.4. Message Processing

We explain the process of message filtering with the aid of Figure 4-5. The description focuses on input filters, but output filters work in exactly the same manner. In Figure 4-5, three filters, A, B, and C, are shown. We assume sequential composition of these three filters. Each filter has a filter type and a filter pattern. The filter type determines how to handle the messages after they have been matched against the filter pattern. The filter pattern is a simple, declarative expression to match and modify messages. Typically, messages travel sequentially along the filters until they are dispatched. Dispatching here means either to start the execution of a local method or to delegate the message to another object.

Figure 4-5. An intuitive schema of message filtering.


Figure 4-5 illustrates how a message[6] is rejected by the first filter (A) and continues to the subsequent filter (B). At filter (B) it matches immediately, and is modified . Then the message continues to the last filter (C). In the example, at the last filter, the message matches and is then subject to a dispatch.

[6] Messages are first reified; i.e., a first-class representation is created. Composition filters thus conceptually apply a form of message reflection [20], but note that actual implementations may "optimize away" the reification.

Each filter can either accept or reject a message. The semantics associated with acceptance or rejection depend on the type of the filter. Typically, these are the manipulation (modification) of the message or the execution of certain actions. Examples of predefined filter types are:

Dispatch. If the message is accepted, it is dispatched to the current target of the message; otherwise, the message continues to the subsequent filter (if there is none, an exception is raised) [2].

Substitute. Is used to modify (substitute) certain properties of messages explicitly.

Error. If the filter rejects the message, it raises an exception; otherwise, the message continues to the next filter in the set [2].

Wait. If the message is accepted, it continues to the next filter in the set. The message is queued as long as the evaluation of the filter expression results in a rejection [8, 10].

Meta. If the message is accepted, the message is reified and sent as a parameter of a new message to a named object; otherwise the message just continues to the next filter. The object that receives the message can observe and manipulate the reified message and reactivate its execution [4].

We will see examples of the application of various filter types throughout the remainder of this chapter. Although filters can also be defined by the programmer, we do not discuss that in this chapter.

A composition filter specification corresponds to the creation of an instance of a filter type; for example, in Listing 4-1, ProtectedClaimDocument declares the following filter in line 18:

 (18)     inh : Dispatch = { inner.* , document.* }; 

This expression declares a filter instance with name inh of filter type Dispatch, which is initialized with the filter pattern between the curly brackets. The filter pattern consists of a number of filter elements, connected with composition operators. In this particular case, the two filter elements are inner.* and document.*. A filter element is mainly used for matching messages. In addition, a filter element may modify certain parts of messages. Evaluation of a filter element always yields at least a Boolean result indicating whether the message actually matched the filter element.

We discuss only one filter element composition operator: the sequence operator ','. The semantics of this operator are similar to a conditional ORwhen the filter element on the left side matches, the whole expression is satisfied, and no further filter elements should be considered. However, if the filter element on the left side does not match, the filter element on the right side will be evaluated, and so on, until either a filter element matches or all filter elements have been evaluated. The total expression always yields a Boolean result, i.e., true if the message did match one of the filter elements and false if it could not match any of thema so-called reject.[7]

[7] For an impression of the possible alternative composition operators, consider for example operators based on pure ORs or ANDs, where multiple matches might lead to multiple dispatch of the same received messages, either sequential or in parallel.

In the example shown previously, the message matches the first filter element if the selector of the received message is within the signature of inner. This is the case when a corresponding method has been declared by the implementation object itself. If so, the target property of the message is replaced by a reference to the inner object. If the selector of the message is not in the signature of inner, the message is matched with the second filter element, document.*. This is successful if the selector of the message is in the signature of class ClaimDocument. If the message does match, it has reached the end of the filter elements, yielding a reject.

After this matching process, an action is performed based on the filter type, the result of the matching process (i.e., accept or reject), and the potentially modified message. In the previous example, either this means the dispatch of the message to the new target, or the original, unchanged message simply continues to the subsequent filter. Dispatching by a Dispatch filter means one of three things:

  1. If the target of the message is inner, this implies the execution of a method of the inner (implementation) object with the name that equals the selector of the message.

  2. If the target of the message has been declared as an external object, dispatching is equivalent to (true) delegation [28] of the message to the target object. This means that the message is forwarded to the target, where the essential difference with message invocation is that the server pseudovariable (usually referred to as 'this' in C++ and Java, and 'self' in Smalltalk) still refers to the original receiver of the message invocation, not to the new target. In the literature, this property is also described as "allowing the ancestor to be part of the extended identity of the delegating object" [28]. A key feature of delegation is that it allows multiple instances to share (reuse) both the behavior (i.e., methods) and state (value) of an instance.

  3. If the target of the message has been declared as an internal object, dispatching is again equivalent to true delegation, but the intuitive meaning is differentbecause each instance has its own copy of a declared internal object, delegating to an internal object is equivalent to inheriting from the class of the internal object. This is because both the behavior and the data structure (but not the actual values) of the superclass are reused.

This is exemplified by the dispatch filter in the previous example. This filter accepts and executes all received messages that are declared and implemented by the inner (implementation) object (in lines 2130 of Listing 4-1). All other messages that are in the signature of internal object document (i.e., available on its interface), as defined by concern ClaimDocument, match at the second part of the filter and are thus dispatched (delegated) to the document object. Remaining messages that are in neither of the two signatures continue to the next filter; if there is none, this yields a run-time error ("message not understood").[8]

[8] Run-time errors can be avoided through static type checking, with some reduction of flexibility.

We now consider the first filter in this example implementation, the filter named protection with filter type Error. The semantics of filter type Error are that it does nothing when a message is accepted and raises an exception[9] when the message is rejected. The filter contains several elements, separated by the ',' sequence composition operator:

[9] It is possible to add the type of exception as a parameter to the filter type; for example, "protection: Error(AuthorizationException)=...".

 protection : Error = {   Payment => {setApprovedAmount, getApprovedAmount},   MedChck => {setMedStatus, getMedStatus,               getClaimMotivation},   ...  //etc. for the other views   True => {mail, print, status, getClientName, getCategory} }; 

Each of these elements has the form <condition> => {<list of messages>}. Its semantics are that the expression on the right side of the characters '=>' is only evaluated if the condition on the left side evaluates to true.

The aim of this example is to ensure that only the relevant messages are allowed to pass, and all others should result in an exception. To achieve this, the above Error filter uses conditions (Payment, MedChk, etc.) that evaluate to true if the invocation was sent by respectively the tasks Payment, MedicalCheck, etc. In combination with the enable operator '=>', the protection filter only allows messages setApprovedAmount() and getApprovedAmount() if the condition Payment is true. This means the sender of the message was a payment task. The filter expresses such constraints for all the different tasks, ending with a list of messages that are always acceptable; hence these are associated with the condition true.

4.3.5. Intra-Object Crosscutting

The previous example of adding views exemplifies so-called intra-object crosscutting: each view constraint applies to a set of messages. For example, the MedChk condition is used to ensure authorized access for the messages setMedStatus(), getMedStatus(), and getClaimMotivation(). Instead of reimplementing the restriction in each of the corresponding methods, a filter defines this crosscutting constraint in a modular way.

However, the range of the crosscut specification is limited: Although a filter expression may refer to messages that are inherited from or delegated to other concerns, only messages on the interface of a single concern are considered. In the next section, we show how to extend the application of composition filters in a broader (crosscutting) scope.



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