Join Point Interfaces
FOAL Keynote Slides
At FOAL 2012, I gave a keynote talk on JPIs. You can access the slides here:
Introduction
Join point interfaces (JPIs) are contracts between aspects and advised code. JPIs are an extension and refinement of the notion of join point types recently introduced by Steiman et al. JPIs support a programming methodology where aspects only specify the types of join points they advise based on a JPI, not on concrete pointcuts. It is the responsibility of the programmer maintaining the advised code to specify, through an exhibits clause, which join points are of which type. Aspects and advised code can be developed and evolved independently.
JPIs do allow for strict separate compilation thanks to modular type checking. When programmers in charge of advised code compile their modules, they need to include JPI definitions but no aspect code. Key to sound separate compilation is the fact that JPIs clearly state both return and exception types at join points, both of which are absent in Steimann’s join point types. Other related approaches also do not deal with checked exceptions. Likewise, when aspect experts compile their aspects, they only include the join point interface definitions but no base code. In addition, join point interfaces support a notion of subtyping, which helps in structuring and managing the complexity of the space of events-of-interest to aspects. Subtyping of JPIs supports join point polymorphism and advice overriding. We introduce a novel semantics of advice dispatch that avoids the pitfalls of other approaches, inspired by the well-established multiple-dispatch semantics. We have implemented JPIs for AspectJ using the abc compiler.
Publications
The following paper gives the most current and detailed information on JPIs: Joint Point Interfaces for Safe and Flexible Decoupling of Aspects (Eric Bodden, Éric Tanter, Milton Inostroza), In ACM Transactions on Software Engineering and Methodology (TOSEM), 2013. (To appear.)
This publication explains our current JPI design on just a few pages: A Brief Tour of Join Point Interfaces (Eric Bodden, Éric Tanter, Milton Inostroza), To appear.,International Conference on Aspect-oriented Programming (AOSD), demo track, 2013.
An early version of the idea of JPI is described in: Join Point Interfaces for Modular Reasoning in Aspect-Oriented Programs (Milton Inostroza, Éric Tanter and Eric Bodden), In ESEC/FSE ’11: Joint meeting of the European Software Engineering Conference and the ACM SIGSOFT Symposium on the Foundations of Software Engineering, Sep. 2011, New Ideas Track.
Presentations
This is our talk on Join Point Interfaces, the “Join Point Interfaces for Modular Reasoning in Aspect-Oriented Programs”.
This presentation is being made available under the Creative Commons “Attribution-ShareAlike” license.
Download
Join Point Interfaces are maintained as part of the AspectBench Compiler (abc). You can access abc’s source code here. Also, we provide nightly builds of abc at this web page.
Example
To show how JPI works, consider a web application for e-commerce.
public class ShoppingSession {
private Set items;
private Customer cus;
...
public void buy(Item it, int amount, double price){
items.add(it);
...
}
}
Defining a Join Point Interface
Consider that we need to introduce extra functionalities in our application. These functionalities must be introduced when a purchase is done.
- To discount a 10% over the total purchase if the customer bought more than five items of the same product.
- To register each purchase that is done through e-commerce application in a log.
In order to introduce these new functionalities programmers must do the following:
- create a JPI definition,
- exhibit the point of interest, and
- define two aspects: Discount (first functionality) and Logger (second functionality).
To establish the contract between aspects and the advised code, it is necessary to define the following JPI definition:
jpi void Buying(Item i, int amt, double price);
The above JPI definition looks like a method signature (except for the extends clause, used for join point subtyping). It has return type, name, arguments and checked exceptions.
With this JPI Buying definition base code programmers can exhibit the point of interest (exhibits clause) and aspects programmers can implement the new functionalities (advice declaration). Next it’s shown how both advised code and aspects have been modified after introducing JPI.
Base code side
public class ShoppingSession {
...
exhibits void Buying(Item it, int amt, double price):
execution(void buy(..))
&& args(it,amt,price);
public void buy(Item it, int amount, double price){
items.add(it);
...
}
}
The exhibits clause allows programmers to show the point of interest (buy method execution). An exhibits clause gives a type to certain join point when it is captured by this clause. In this case, the type of the join point will be Buying.
Aspect side
public aspect Discount {
void around Buying(Item it, int amt, double price){
double factor = (amt>5) ? 0.9 : 1;
proceed(it,amt,price*factor);
}
}
public aspect Logger {
void around Buying(Item it, int amt, double price){
String message = it.getDescription() + " amount "+amt;
Utils.RegisterEvent(it,amt,price);
proceed(it,amt,price);
}
}
Aspects are now free of pointcuts and their pieces of advices refer to some JPI definition.
Invariance
As you might notice, the Buying JPI definition has been declared with the same return and arguments type of the advised shadow. We enforce type invariance to avoid the type unsoundness that users may experience with AspectJ. This topic have been discussed by others [De Fraine et al., 2008] and we revisit this in Technical Report TUD-CS-2012-0106.
Join Point Polymorphism
From now on the products will be categorized in: EcoFriendly and BestSeller. For each of these categories we want to reward our customers with a discount coupon for future shopping. To implement these new discounts, the Discount aspect should be modified as follows:
public aspect Discount {
void around Buying(Item it, int amt, double price){...}
void around BuyingBestSeller(Item it, int amt, double price, ShoppingSession ss){
Utils.SendBestSellerCoupon(ss.getCustomer(),it);
proceed(it,amt,price,ss);
}
void around BuyingEcoFriendly(Item it, int amt, double price, ShoppingSession ss){
Utils.SendEcoFriendlyCoupon(ss.getCustomer(),it);
proceed(it,amt,price,ss);
}
}
The JPI definitions BuyingEcoFriendly and BuyingBestSeller are related with Buying: they represent the same execution point, but they expose a more detailed context.
To show this, programmers can define inheritance between JPIs. A possible definition could be the following:
jpi void Buying(Item i, int amt, double price); jpi void BuyingBestSeller(Item i, int amt, double price, ShoppingSession ss) extends Buying(i,amt,price); jpi void BuyingEcoFriendly(Item i, int amt, double price, ShoppingSession ss) extends Buying(i,amt,price);
In presence of inheritance between JPI it is important to see how the advice-dispatch semantics works. Our advice-dispatch semantics establishes that the most specific advice will always get executed. For instance in our e-commerce application it is possible that a join point can be BuyingBestSeller and BuyingEcoFriendly type at the same time. That means there are some products that are both Best Seller and also Eco Friendly. With our advice-dispatch semantics, the following pieces of advices will be executed:
- Discount : BuyingBestSeller and BuyingEcoFriendly.
- Logger : Buying.
The piece of advice related with the type Buying that belongs to Logger aspect will be executed only once. With join points types the same advice will be get executed twice!
The consequence of using our semantics is that we are not fulfilling with the customers requirements because the piece of advice related with Buying in the Discount aspect will never be executed (it executes the more specific piece of advice). To resolve this problem, programmers can define the piece of advice related with Buying as final. A piece of advice that is final will always get executed regardless of whether there is or not a more specific piece of advice.
public aspect Discount {
void final around Buying(Item it, int amt, double price){...}
void around BuyingBestSeller(Item it, int amt, double price, ShoppingSession ss){...}
void around BuyingEcoFriendly(Item it, int amt, double price, ShoppingSession ss){...}
}
As this example is one of our test cases, you can see it in more detail.
Checked Exception Handling
So far, we have omitted exceptions treatment in our motivation example. Now, suppose that the aspect programmer needs to modify the BuyingEcoFriendly advice to obtain the customer related with a shopping session:
public aspect Discount {
...
void around BuyingEcoFriendly(Item it, int amt, double price, ShoppingSession ss) throws SQLException{
Customer cus = SQL.query(/*query omitted*/);
Utils.SendEcoFriendlyCoupon(cus,it);
proceed(it,amt,price,ss);
}
}
The JPI type system flags the BuyingEcoFriendly advice as erroneous due to the JPI declaration (BuyingEcoFriendly) states that join points of this type cannot throw any checked exceptions. The aspect programmer is notified that part of the JPI contract is now broken. The aspect programmer can now either handle SQLExceptions in the advice itself, or, if that is an option, update the JPI to:
jpi void BuyingEcoFriendly(...) throws SQLException;
When choosing the second option, the contract between aspect and base code is modified. Therefore, as soon as the base code programmer updates to the new interface, the type checker reports that exceptions of type SQLExceptions must be caught at all code locations of join points of type BuyingEcoFriendly, as specified by the exhibits declarations.
Flexible Quantification
By nature, join point interfaces follows the approach of local quantification as same as join point types. However, this approach does not fit well when highly-crosscutting aspects like dynamic analyses need to be implemented. For that reason, JPIs support a combination of local and global quantification. To provide such quantification mechanism, we extend our language design to introduce two new concepts: global JPI definitions and open/sealed mechanism. While global JPIs allow programmers to perform global quantification over all classes/aspects in the system, open/sealed mechanism allows programmers to control the global quantification in a more fine-grained manner by open or protecting certain modules against such quantification.
Global JPIs
To illustrate how global interfaces work, let us suppose that we want to use the Logger aspect, defined early, to log every action that occurs in the e-commerce application. Under the local quantification setting, we would need to modify every single class in order to introduce the exhibits clauses. This process is tedious, error prone, and not scale; as soon as new classes are added, new exhibits clauses will need to be introduced. To overcome this problem, programmers can use instead a global JPI definition:
global jpi Object AllExec(): execution(* *(..));
Controlled Global Quantification
To preventing that certain classes/aspects be advised by a global JPI definition, any class/aspect may seal itself by applying to its own definition:
sealed class SecretTransaction {...}
Join points raised lexically inside the SecretTransaction class are hidden from global pointcuts. A sealed type can also opt to exhibit join points explicitly. For instance, the following class is immune to the AllExec global pointcut, but exposes certain Caching join points:
sealed class SecretTransaction {
exhibits Object Caching() : ...
}
Further, programmers can opt to refine global pointcuts in order to expose only certain join points. To do so, the declared global pointcut is available as a named pointcut global inside an exhibits clause:
sealed class SecretTransaction {
exhibits Object AllExec() : global() && !call(* secretAction(..));
}
Here SecretTransaction class allows that all method execution join points will be matched by the AllExec global pointcut, but selectively hides the method execution of a secretAction method.
So far, we are consider that, by default, classes/aspects are open to global quantification and that classes/aspects which want to prevent global quantification need to be sealed. The dual approach is also appealing: by default, classes are sealed, and classes that approve global advising need to be explicitly declared as open. We actually support both default semantics in our implementation. This means that is possible to declare a class or aspect as open:
sealed class NothingToHide {...}
If the language default is set to sealed-by-default, then the above declaration allows the class NothingToHide to be exposed to global quantification. (Otherwise it is just redundant.)
Regaining Flexibility through generic types
Specially in combination with global quantification, the invariant typing enforce by JPI can be too rigid, preventing that some join points do not be advised. Recall the global JPI definition:
global jpi Object AllExec(): execution(* *(..));
In this case, only join point shadows with return type Object will be avised. As our case studies show, this is too rigid in practice. To overcome this problem and regain flexibility, it is possible to introduce generic types in a JPI definition. A proper definition of the AllExec global JPI would be as follows:
<R, E> global jpi R AllExec() throws E: execution(R *(..));
In this definition, we explicitly abstract the return type and any checked exception type that the method may declare.
Grammar Definition
We present the syntactic extension to AspectJ (shown in gray) in order to support join point interfaces.
Implementation details
We implemented Join Point Interfaces (Click here to download the current version of the compiler) as an extension abc.ja.jpi to the AspectBench Compiler (abc). The source code is also maintained there.(Click here to inspect the source code) We implemented Join Point Interfaces using the JastAdd frontend. Our abc extension extends Bodden’s implementation of Closure Join Points. This allows our approach to not only support implicit announcement (pointcuts select exhibited join points), but explicit announcement as well. Our implementation works as follows:
- We typecheck join point interfaces against either exhibits clauses and advice declarations.
- We compound pointcut expressions for each advice definition.
Typing Rules
- Inheritance on Join Point Interfaces: A join point interface declaration can also specify a super interface, using extends. In that case the name of the extended JPI is given, and the arguments of the super interface are bound to the arguments of the declared JPI. The types of the arguments must coincide, as must the return type.
- Base code: The base code can exhibit join points. These joint points must obey the contract impose by its join point interface definition. One part of this contract has to be fulfilled by the pointcut associated with the exhibit declaration: the pointcut has to bind all the arguments in the signature, using pointcut designators such as args, this and target. The second part of the contract from the base code point of view is to respect the return type and exception types of the JPI. This has to be checked at each join point potentially matched by the pointcut associated to the exhibit declaration. More precisely, the pointcut is matched against all join point shadows in the lexical scope of the declaring class. Whenever the pointcut matches a join point shadow, the type system checks that this shadow is of the same type as the return type of the JPI.
- Aspects: An aspect is type checked just like an AspectJ aspect, save for its advices. Type checking the signature of an advice is simple. Each advice declares an advised JPI in its signature; the signature must directly resemble the JPI definition. More precisely, both return and argument types must be exactly the same as those of the JPI. Checked exceptions are dealt with similarly.
- “proceed”: Type checking the advice body is similar to type checking a method body, with the additional constraint of considering calls to proceed. As it turns out, a join point interface is identical to a method signature (except for the extends clause, used for join point subtyping). In fact, a JPI specifies the signature of proceed within the advice body, thereby abstracting away from the specific join points that may be advised. This is a fundamental asset of JPIs, and the key reason why interfaces for AOP ought to be represented as method signatures (including return and exceptions types), and not as structures like join point types.
- Checked exceptions: Type checking on checked exceptions depends on which side is being verified: Base code or Aspect.
- Base code side: Checked exceptions of the join point shadow must be exactly the same as those of the JPI. With this invariant semantics we ensure that an advice is able to handle properly those exceptions that could be raised when proceed is executed.
- Aspect side: An advice declaration, in its list of checked exceptions, can only contain exception types that do not lead to uncaught exceptions in the base code, i.e.: for each exception type (eadv) in advice checked exception list, there must be at least one checked exception (ejpi) in the checked exception list of the JPI, such that eadv <: ejpi. That means, no advice is allowed to raise checked exceptions that could lead to uncaught checked exceptions on the side of the base code.
Advice-Dispatch Semantics
Our advice declarations do not at all refer to any pointcut. Instead they refer to a JPI declaration which in turn may be bound to pointcuts by one or more exhibits declarations. To allow for maximal reuse of existing functionality in the abc compiler, we decided to implement our dispatch semantics through a transformation that would compute for each such advice a single pointcut, based on the referenced JPI, its type hierarchy and the exhibits declarations of those types. Let a be the advice to compute the pointcut for, as the set of other advices in the same aspect and es the set of all exhibits declarations in the program. Then we compute the pointcut for a as follows:
The equation for pc+ implements polymorphism: if a refers to a.jpi then a will match not only on join points for a.jpi itself but also for all subtypes. The equation for pc implements advice overriding within the same aspect: if an advice a' has the same kind as a but refers to a more specific JPI type, then a' overrides a, which means that a will not execute for this JPI’s join points. For advice that has been declared final, pc- is simply skipped, so as to avoid overriding. We support the possibility to declare an advice as final, meaning it will always execute if applicable, regardless of whether there exists a more specific applicable advices; in such a case, both execute, following the standard AspectJ composition rules.
Evaluation
To evaluate our proposal, we have implemented join point interfaces in the following two applications: AJHotDraw and Law of Demeter. Here you can download the JPI versions of these applications (source and compiled binary code).
- AJHotDraw JPI version with:
- LawOfDemeter JPI version with:
Integration with CJP
To allow explicit invocation, we integrate Closure Join Point to our project. We execute test cases of closure join points in our project to verify that the integration is completely correct.







