• No results found

JBoss Cache implementation: principles and crosscutting

The runtime description above and system architecture of JBoss Cache shown in figure 3.1 provide an easy-to-grasp high-level view of its operation. However, a crucial question is how faithfully such abstract representation can be transposed into an a object-oriented (Java- based) implementation. In this section we answer this question in three steps. We first present the design principles guiding the JBoss Cache implementation. It turns out that the implementation does not rely on standard Java-based structuring mechanisms (such as packages, classes and objects) but uses so-called interceptors, a more complex reflection-based means for application structuring. Second, we provide first evidence for the crosscutting na- ture of JBoss Cache’s main functionalities. Third, we discuss why JBoss AOP, a component of the JBoss application server, is not suitable to address the crosscutting issues of JBoss Cache. Basically, JBoss AOP falls short because it is is a subset of the (sequential) AspectJ model that is not appropriate for the modularization of non-sequential crosscutting functionalities as discussed in chapter 2.

3.2.1 Design principles

JBoss Cache uses an interceptor filter pattern as a main structuring mechanism at the code level, see figure 3.2. The main idea behind this pattern is that method calls to the cache data structure are pre-processed by a chain of filters, where each filter implements a specific concern, e.g., replication or transactions. In the figure, class TreeCache implements the tree data structure and the filters are implemented by classes called interceptors. Each method invocation to a TreeCache object is then processed by the elements of the interceptor chain. For example, a call to the method put (edge numbered 1) on the cache, is first transformed,

1 publi Object put(Fqn fqn, Object key, Object value)

2 throws CacheException {

3 GlobalTransaction tx=getCurrentTransaction(); 4 MethodCall m=new MethodCall(putKeyValMethodLocal,

5 new Object[]{tx, fqn, key,

6 value, Boolean.TRUE});

7 return invokeMethod(m);

8 }

Figure 3.3: Low-level transaction handling in class TreeCache

using reflection, into a MethodCall object and is then passed to the chain of filters (edge numbered 2). Conceptually, each filter then fully implements its respective functionality, e.g., the replication filter replicates the class to other caches (edge numbered 3). As we show later, however, the modularization of the key functionalities is far from perfect. Finally, once the call has passed all the filters in the filter chain, the actual behavior is invoked in the data structure (edge numbered 4).

3.2.2 Implementation structure and crosscutting issues

The implementation of this design principles is subject to severe crosscutting issues. Such issues are present in the implementation of the basic methods managing the replication data structure but also in the replication and transaction filters themselves.

Tangling in the replication data structure. This code structure is apparent in the low-level cache manipulation methods of class TreeCache. For example, figure 3.3 shows the definition of method put. As defined in the method signature, it returns an object and receives, as parameters, a fully qualified name, a key object, and a value object. In the body definition the code has to get the transactional context (Line 3), modify it if necessary, create (using reflection) an object of type MethodCall (Lines 4 to 6), and invoke it reflectively, that is, pass it along the interceptors chain.2 Note that this description already strongly hints at a code tangling problem of the implementation of the put method (and similarly, the other low-level cache management methods). In particular, transactions, the creation of an object for replication (an object of type MethodCall), and the calls to and from the filter patterns are tangled.

Crosscutting and filters. Figure 3.4 shows two different representations of the code struc- ture of the filter patterns in JBoss Cache 1.2.1 . The class diagram (Figure 3.4a) shows the main classes participating in the implementation: a class representing the main data struc- ture (TreeCache), that use a chain of interceptors represented by the class Interceptor that can be chained to other Interceptor objects (see the aggregation labeled “next” that rep- resents the field next of type Interceptor). The Interceptor class is then specialized by specific interceptor classes (e.g., classes implementing replication, transactions or locking). The class diagram also shows that the class implementing transparent caching of POJOs

2This put method differs slightly form the one presented in section 3.1: there, the method receives three

Stringobjects as parameter, however the implementation of that method redirects the call to the one presented here after processing and converting the String objects.

(TreeCacheAOP) is a specialization of the main data structure (see an explanation of JBoss AOP and JBoss cache below in 3.2.3).

Even though, the implementation of code shown before follows well known practices for design and modularization i.e., design patterns and inheritance, an analysis of the code shows that modularization is not achieved and the code is subject to crosscutting by the code for replication and transactions, see figure 3.4b. The figure, a standard crosscutting view gener- ating using the AJDT Eclipse environment for AspectJ, depicts the scattering of replication and transaction code in the main class TreeCache (represented by the leftmost column in the figure) and in the classes belonging to the interceptor package (right block in the figure). Replication-related code is colored gray, transaction-related code is marked black. The fig- ures clearly exhibit scattering and tangling of replication and transactional code with respect to each other and with respect to the functional code of the cache. Furthermore, the code related to the implementation of the interceptor filters — that was added to help modulariza- tion — is subject to severe crosscutting itself. Thus the mechanism used for modularization generates itself modularization problems. Concrete figures that provide evidence for these claims are given in section 3.3.3. In the addition to crosscutting directly due to replication and transactional code, calls between the interceptor package and the remaining code parts , which we have not included here to stress our main point, are also crosscutting.

3.2.3 Caching of POJOs and JBoss AOP.

Besides the filters discussed above, JBoss AOP, another AO-related component of the JBoss application server could potentially have been used to improve the modularization properties of JBoss Cache. JBoss AOP is a framework for Aspect-Oriented programming of JBoss and is used in JBoss Cache to cache “plain old” Java objects (“POJOs”) in a transparent manner. Concretely, this mechanism handles object inheritance, aggregation, as well as the object graph, e.g., for serialization, in the context of caching.

However, this use of JBoss AOP does not contribute to the goal of a better modularization of the cache: JBoss AOP is solely used to facilitate the use of JBoss Cache in an application but does not address modularization or extension of JBoss Cache core functionalities. Con- ceptually, JBoss AOP is not suited to achieve this goal: as a subset of the (sequential) AspectJ model, any implementation involving several cache members, which is at the very heart of the crosscutting problem we want to solve, would be subject to the problems presented in Sec. 1.1.