JPz'log Coin Coin and Plop da Plop

11Feb/105

Revisiting Guice and AOP with AspectJ

I decided to revisit my article on Guice and AOP with AspectJ instead of the AOP Alliance API that Guice comes with.

The full source code is available on GitHub.

To refresh your memory, we had implemented a declarative approach for controlling the access to a class methods through annotations:

@WithUserProfileVerification
public class InMemoryContactManager implements ContactManager {
 
    private final Set<Person> contacts = new HashSet<Person>();
 
    @RequiresProfile(ADMIN)
    public ContactManager add(Person person) {
        contacts.add(person);
        return this;
    }
 
    @RequiresProfile(ADMIN)
    public ContactManager remove(Person person) {
        contacts.remove(person);
        return this;
    }
 
    @RequiresProfile(USER)
    public Person lookup(String name) {
        for (Person person : contacts) {
            if (person.getName().equals(name)) {
                return person;
            }
        }
        return null;
    }
 
}

First off AspectJ is way more expressive than the AOP Alliance API. Using Guice with AspectJ is not very different. If your aspects do not need injection then you fall back to plain AspectJ development, as Guice and AspectJ live apart from each other. Things are a little more subtile if you need to connect Guice with your aspects.

In our case we had one aspect that needed injection, hence the trick is to ask Guice to perform injection on the aspect instance. By default, an AspectJ aspect is a singleton, so all we need is to grab an access to the instance, which is as simple as calling the Aspects#aspectOf() static method.

Now let's see some code that differs from the Guice / AOP Alliance sample I showed you.

Here is how you can define a simple dirty console logger that hooks itself onto any implementation of the ContactManager interface:

package info.ponge.julien.hacks.guiceaspectj.aspects;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
@Aspect
public class ContactManagerLogger {
 
    @Before("call( * info.ponge.julien.hacks.guiceaspectj.contact.ContactManager.*(..) )")
    public void methodCalled(JoinPoint thisJoinPoint) {
        System.out.println("Calling: " + thisJoinPoint.getSignature().getName());
    }
 
}

The aspect that checks for the user permissions is implemented as follows:

package info.ponge.julien.hacks.guiceaspectj.aspects;
 
import com.google.inject.Inject;
import info.ponge.julien.hacks.guiceaspectj.auth.RequiresProfile;
import info.ponge.julien.hacks.guiceaspectj.auth.UserProfile;
import info.ponge.julien.hacks.guiceaspectj.auth.UserProfileChecker;
import info.ponge.julien.hacks.guiceaspectj.auth.WithUserProfileVerification;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
 
import static info.ponge.julien.hacks.guiceaspectj.auth.UserProfile.ADMIN;
import static info.ponge.julien.hacks.guiceaspectj.auth.UserProfile.USER;
 
@Aspect
public class ProfileVerification {
 
    @Inject
    UserProfileChecker userProfileChecker;
 
    @Before("execution( * *(..) ) && @annotation( required ) && within( @WithUserProfileVerification * )")
    public void verify(RequiresProfile required) {
        UserProfile expected = required.value();
        UserProfile current = userProfileChecker.getCurrentUserProfile();
 
        if (insufficientProfile(expected, current)) {
            throw new RuntimeException("The current user profile (" + current + ") is not sufficient: " + required);
        }
    }
 
    private boolean insufficientProfile(UserProfile required, UserProfile current) {
        return (required == ADMIN && current != ADMIN)
                || (required == USER && (current != USER && current != ADMIN));
    }
 
}

Finally our Guice module is configured to perform the injection on the singleton that corresponds to the previous aspect:

package info.ponge.julien.hacks.guiceaspectj;
 
import com.google.inject.*;
import info.ponge.julien.hacks.guiceaspectj.aspects.ProfileVerification;
import info.ponge.julien.hacks.guiceaspectj.auth.UserProfileChecker;
import info.ponge.julien.hacks.guiceaspectj.auth.dumb.DumbUserProfileChecker;
import info.ponge.julien.hacks.guiceaspectj.contact.ContactManager;
import info.ponge.julien.hacks.guiceaspectj.contact.Person;
import info.ponge.julien.hacks.guiceaspectj.contact.simple.InMemoryContactManager;
import org.aspectj.lang.Aspects;
 
import static org.aspectj.lang.Aspects.*;
 
public class Main {
 
    public static void main(String[] args) {
        new Main().execute();
    }
 
    private Module guiceModule = new AbstractModule() {
        @Override
        protected void configure() {
 
            bind(ContactManager.class)
                    .to(InMemoryContactManager.class)
                    .in(Singleton.class);
 
            bind(UserProfileChecker.class)
                    .to(DumbUserProfileChecker.class)
                    .in(Singleton.class);
 
            requestInjection(aspectOf(ProfileVerification.class));
 
        }
    };
 
    private Injector injector = Guice.createInjector(guiceModule);
 
    private void execute() {
        ContactManager contacts = injector.getInstance(ContactManager.class);
        UserProfileChecker profileChecker = injector.getInstance(UserProfileChecker.class);
 
        profileChecker.login("Julien", "secret");
 
        contacts.add(new Person("Julien Ponge", "julien.ponge@gmail.com"));
        contacts.add(new Person("Jean-Jacques", "jean.jacques@gmail.com"));
 
        profileChecker.logout();
        profileChecker.login("Jean-Jacques", "1234");
 
        System.out.println(contacts.lookup("Julien Ponge"));
 
        profileChecker.logout();
        contacts.add(new Person("Mr Bean", "mrbean@gmail.com"));
    }
 
}

As one would expect, trying to add Mr Bean fails due to insufficient permissions:

Calling: add
Calling: add
Calling: lookup
Julien Ponge 
Calling: add
Exception in thread "main" java.lang.RuntimeException: The current user profile (ANONYMOUS) is not sufficient: @info.ponge.julien.hacks.guiceaspectj.auth.RequiresProfile(value=ADMIN)
	at info.ponge.julien.hacks.guiceaspectj.aspects.ProfileVerification.verify(ProfileVerification.java:27)
	at info.ponge.julien.hacks.guiceaspectj.contact.simple.InMemoryContactManager.add(InMemoryContactManager.java:21)
	at info.ponge.julien.hacks.guiceaspectj.Main.execute(Main.java:54)
	at info.ponge.julien.hacks.guiceaspectj.Main.main(Main.java:17)

(full code on GitHub)

Filed under: English, Fun, Geeking, Howto, Java 5 Comments
4Nov/0912

Guice it up (or AOP can be made simple sometimes)

Juice

I have been knowing about the Google Guice dependency injection container features for quite some time. Guice is a really pleasant DI framework that does its job with brilliant simplicity from a developer point of view (oh yes, and you don't have to describe the classes wiring in a dumb XML file like Spring does).

Guice has more than DI capabilities though, as long as you spice it up with extensions libraries. Today I'll show you the AOP capabilities that Guice offers.

I must admit that I have always been puzzled by this thing called aspect-oriented programming. While the idea of separating actual code from cross-cutting concerns (e.g., security, transactions) makes a lot of sense, one may easily end-up writing spaghetti code.

If you don't believe me, have a look at Spring ROO, I am really curious to know if one can come up with serious arguments for not calling that mess of Java, AspectJ and Spring XML oddities "spaghetti code".

Anyway, AOP flourished rapidly a few years back, as advanced developers, methodologists and event academics all went crazy about it through books, frameworks and claims of the death of OOP. The good news is that for a change, the AOP hype faded away in a flashlight (I whish the same could have been true for those silly things called SOAP and BPEL). However, AOP is still very useful in non-dynamic languages like Java, and reasonable use can make code quite elegant.

Let's get back to Guice, as I will quickly go through some code snippets. I wanted to play with Guice through the trivial use-case of a declaratively access-restricted contacts manager. The code that you will see exhibits AOP and DI features in Guice. As far as the quality is concerned, it will show you the approach, but in an industrial case one would of course need something a bit more elaborated.

First of all, I created a wonderful Person model class:

package app;
 
public class Person {
 
    private final String name;
 
    private final String email;
 
    public Person(String name, String email) {
        this.name = name;
        this.email = email;
    }
 
    public String getName() {
        return name;
    }
 
    public String getEmail() {
        return email;
    }
 
    @Override
    public String toString() {
        return new StringBuffer(name)
                .append(" <")
                .append(email)
                .append(">")
                .toString();
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
 
        Person person = (Person) o;
 
        if (!email.equals(person.email)) return false;
        if (!name.equals(person.name)) return false;
 
        return true;
    }
 
    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + email.hashCode();
        return result;
    }
}

Disgression: it would have been much more concise in Scala or Groovy.

I then designed a ContactManager interface:

package app;
 
public interface ContactManager {
 
    public ContactManager add(Person person);
 
    public ContactManager remove(Person person);
 
    public Person lookup(String name);
 
}

Note that I made it minimalistically fluent so that add and remove calls could be chained.

I then wanted to design a basic access control mechanism, so that an implementation of ContactManager could declaratively restrict its access to a certain type of user profile, much like what we can do with EJB 3.x (incidentally, good implementations like Glassfish use AOP and bytecode engineering under the hood).

Hence, I designed annotations to let classes and methods specify access-control policies:

package app.auth;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WithUserProfileVerification {
}

and

package app.auth;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresProfile {
 
    UserProfile value();
 
}

along with a user profile enumeration (anonymous user, regular user and administrator):

package app.auth;
 
public enum UserProfile {
    ANONYMOUS, USER, ADMIN
}

Now we are able to define a basic ContactManager implementation with declarative access-control policies:

package demo;
 
import app.ContactManager;
import app.Person;
import app.auth.RequiresProfile;
import static app.auth.UserProfile.ADMIN;
import static app.auth.UserProfile.USER;
import app.auth.WithUserProfileVerification;
 
import java.util.HashSet;
import java.util.Set;
 
@WithUserProfileVerification
public class ContactManagerImpl implements ContactManager {
 
    private final Set<Person> contacts = new HashSet<Person>();
 
    @RequiresProfile(ADMIN)
    public ContactManager add(Person person) {
        contacts.add(person);
        return this;
    }
 
    @RequiresProfile(ADMIN)
    public ContactManager remove(Person person) {
        contacts.remove(person);
        return this;
    }
 
    @RequiresProfile(USER)
    public Person lookup(String name) {
        for (Person person : contacts) {
            if (person.getName().equals(name)) {
                return person;
            }
        }
        return null;
    }
}

From this definition, anonymous users cannot do anything, regular users can perform lookups and administrators can add/remove contacts.

This can be summarized by this class diagram:

guiceaop-1

Cables

The next question is of course: how do you make that actually work?

First, let's design a profile checker interface along with a dumb implementation:

package app.auth;
 
public interface UserProfileChecker {
 
    public UserProfile getCurrentUserProfile();
 
    public UserProfile login(String login, String password);
 
    public UserProfile logout();
 
}
package demo;
 
import app.auth.UserProfile;
import static app.auth.UserProfile.*;
import app.auth.UserProfileChecker;
 
public class DumbUserProfileChecker implements UserProfileChecker {
 
    private UserProfile userProfile = ANONYMOUS;
 
    public UserProfile getCurrentUserProfile() {
        return userProfile;
    }
 
    public UserProfile login(String login, String password) {
        if (login.equals("Julien") && password.equals("secret")) {
            userProfile = ADMIN;
        } else if (login.equals("Jean-Jacques") && password.equals("1234")) {
            userProfile = USER;
        } else {
            userProfile = ANONYMOUS;
        }
 
        return getCurrentUserProfile();
    }
 
    public UserProfile logout() {
        userProfile = ANONYMOUS;
        return userProfile;
    }
 
}

guiceaop-2

Sounds great isn't it? :-) Still, the link is missing between our contact manager implementation, and this user profile checker.

The idea is to design an aspect for that: each class that uses our declarative access-control policies will have method calls beeing intercepted by the aspect. Here is the code:

package demo;
 
import app.auth.RequiresProfile;
import app.auth.UserProfile;
import static app.auth.UserProfile.ADMIN;
import static app.auth.UserProfile.USER;
import app.auth.UserProfileChecker;
import com.google.inject.Inject;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
 
public class UserProfileInterceptor implements MethodInterceptor {
 
    @Inject
    private UserProfileChecker profileChecker;
 
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        UserProfile required = methodInvocation.getMethod().getAnnotation(RequiresProfile.class).value();
        UserProfile current = profileChecker.getCurrentUserProfile();
 
        if (insufficientProfile(required, current)) {
            throw new RuntimeException("The current user profile (" + current + ") is not sufficient: " + required);
        } else {
            return methodInvocation.proceed();
        }
    }
 
    private boolean insufficientProfile(UserProfile required, UserProfile current) {
        return (required == ADMIN && current != ADMIN)
                || (required == USER && (current != USER && current != ADMIN));
    }
 
}

The user profile checker will be injected by Guice in the aspect (see the @Inject annotation). The interesting work is performed by the invoke method that looks at the required user profile (through the invoked method annotation) and checks it against the user profile checker. When the profile matches, the method actually gets invoked, otherwise a RuntimeException is raised.

From there what is missing is simply the Guice wiring definitions. Before we look at that, I also designed an aspect for logging method calls (hence, we will inject two aspects):

package demo;
 
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
 
import java.util.logging.Level;
import java.util.logging.Logger;
 
public class LoggingInterceptor implements MethodInterceptor {
 
    private Logger logger = Logger.getLogger(LoggingInterceptor.class.getName());
 
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        logger.logp(
                Level.INFO,
                methodInvocation.getClass().getName(),
                methodInvocation.getMethod().getName(),
                "invocation",
                methodInvocation.getArguments());
 
        Object result = null;
 
        try {
            result = methodInvocation.proceed();
        } finally {
            logger.logp(
                    Level.INFO,
                    methodInvocation.getClass().getName(),
                    methodInvocation.getMethod().getName(),
                    "return",
                    result);
 
            return result;
        }
    }
 
}

guiceaop-3

Finally, here is the Main class that has the Guice wiring configuration and a simple use-case:

package demo;
 
import app.ContactManager;
import app.Person;
import app.auth.RequiresProfile;
import app.auth.UserProfileChecker;
import app.auth.WithUserProfileVerification;
import com.google.inject.*;
import static com.google.inject.matcher.Matchers.*;
 
public class Main {
 
    public static void main(String[] args) {
        new Main().execute();
    }
 
    private Module guiceModule = new AbstractModule() {
 
        @Override
        protected void configure() {
 
            bind(ContactManager.class)
                    .to(ContactManagerImpl.class)
                    .in(Singleton.class);
 
            bind(UserProfileChecker.class)
                    .to(DumbUserProfileChecker.class)
                    .in(Singleton.class);
 
            UserProfileInterceptor userProfileInterceptor = new UserProfileInterceptor();
            requestInjection(userProfileInterceptor);
 
            bindInterceptor(
                    annotatedWith(WithUserProfileVerification.class),
                    annotatedWith(RequiresProfile.class),
                    userProfileInterceptor);
 
            bindInterceptor(
                    subclassesOf(ContactManager.class),
                    any(),
                    new LoggingInterceptor());
        }
 
    };
 
    private Injector injector = Guice.createInjector(guiceModule);
 
    private void execute() {
        ContactManager contacts = injector.getInstance(ContactManager.class);
        UserProfileChecker profileChecker = injector.getInstance(UserProfileChecker.class);
 
        profileChecker.login("Julien", "secret");
 
        contacts.add(new Person("Julien Ponge", "julien.ponge@gmail.com"));
        contacts.add(new Person("Jean-Jacques", "jean.jacques@gmail.com"));
 
        profileChecker.logout();
        profileChecker.login("Jean-Jacques", "1234");
 
        System.out.println(contacts.lookup("Julien Ponge"));
    }
 
}

guiceaop-4

You can easily modify the execute method call to check that access-control is enforced (e.g., have an anonymous user attempt to add an entry and see that an exception is raised).

The wiring defined in the module configuration is straightforward. One should only pay attention to the requestInjection(userProfileInterceptor) call. Indeed, aspects are not managed by the DI container. As we request an injection in the corresponding class, this call will make it on-demand.

As we saw in this small showcase, Google Guice is a very compelling DI framework with simple and efficient AOP capabilities.

Don't you think so? :-)

27Oct/091

OSGi development made easier

Lego

I must confess that I happen to write OSGi bundles, and somehow happen to actually enjoy that, despite all the care you need to have with respect to:

  • dynamicity-handling boilerplate code
  • having to maintain a MANIFEST
  • forced mutable class design
  • concurrency issues (you often have to spin a thread when your bundle starts).

Creating an OSGi bundle is not very complicated per-se, but tooling has historically been quite limited. Fortunately, we now have a good Maven-based toolchain.

Indeed, wouldn't it be nice to focus on just writing your own bundles and forget about the rest?

First of all I suggest that you use the Apache Felix Maven Bundle. With minimal definitions, it takes the pain of maintaining the MANIFEST entries away from you with minimal informations:

  • it makes a bundle by using most of the informations available from your POM
  • by default, it maintains the imported / exported packages much like you would expect
  • it leverages each of your POM depency nicely: if it's a bundle then fine, otherwise it will either wrap it as a bundle or simply embed the classes (dealing with non-bundles is a huge pain point in OSGi).

The second Maven plugin that I wanted to tell you about is the Pax Plugin by the OPS4J crew. This plugin is another time-saver, as it is able to automatically provision an OSGi runtime (Apache Felix by default, but you can ask for Eclipse Equinox, Knoplerfish, etc).

Without this plugin, you either have to manually install your bundles into an OSGi runtime, or write configuration files to get the provisionned environment that you need for your tests. All of that is handled by the Pax plugin, and you get the extra benefit of beeing able to try your bundles on several OSGi runtimes.

Indeed, to try your bundles on Apache Felix, simply type:

mvn pax:provision

and if you want to play with Eclipse Equinox:

mvn pax:provision -Dframework=equinox

Those two complementary plugins should definitly make OSGi development simpler, at least from the toolchain point of view :-)

Filed under: English, Howto, Java 1 Comment
29Sep/093

ScalaTest in Maven

ScalaTest is a wonderful testing framework written in Scala that embraces several styles of testing (JUnit-style, behavior-driven, etc). Of course we live in a polyglot programming world, hence you don't have to code in Scala to leverage its power :-)

I recently coded in Scala and found that running ScalaTest from Maven is all but a straightforward thing to do.

First of all, I suggest that you jump on the 1.0-SNAPSHOT versions as they contain a JUnit bridge (and other refinements).

Add this to your pom.xml:

(...)
        <dependency>
            <groupId>org.scalatest</groupId>
            <artifactId>scalatest</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>test</scope>
        </dependency>
(...)
        <repository>
            <id>scala-tools.org releases</id>
            <name>Scala-tools Maven2 Releases Snapshots Repository</name>
            <url>http://scala-tools.org/repo-snapshots</url>
        </repository>
(...)
    <pluginRepositories>
        <pluginRepository>
            <id>scala-tools.org</id>
            <name>Scala-tools Maven2 Repository</name>
            <url>http://scala-tools.org/repo-releases</url>
        </pluginRepository>
    </pluginRepositories>
(...)

You then need to make sure that your Scala code is properly compiled from the tests classes location:

(...)
    <build>
        <sourceDirectory>src/main/scala</sourceDirectory>
        <testSourceDirectory>src/test/scala</testSourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.scala-tools</groupId>
                <artifactId>maven-scala-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
(...)

In my case, I am writing tests as specifications that I want to be run from JUnit:

import org.scalatest.junit.JUnitRunner
import org.junit.runner.RunWith
import org.scalatest.Spec
import org.scalatest.matchers.ShouldMatchers
 
@RunWith(classOf[JUnitRunner])
class AutomatonSpec extends Spec with ShouldMatchers {
    (...)
}

Finally, make sure that the Maven Surefire plugin will pick up the specification classes and pass them to JUnit:

(...)
    <reporting>
        <plugins>
(...)
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <includes>
                        <include>**/*Spec.class</include>
                    </includes>
                </configuration>
            </plugin>
        </plugins>
    </reporting>
(...)

JPz'log is Digg proof thanks to caching by WP Super Cache