Monday, March 7, 2016

Gradle and AspectJ

Unfortunately, there's no built-in support for AspectJ in Gradle. But there are a lot of resources, even plugins (e.g., Gradle AspectJ plugin), how to get them working together. The only complaint I have about them is that they substitute the AspectJ compiler and bytecode weaver for the native Java compiler. It doesn't work sometimes. For example, I like Lombok but it and the Aspectj compile-and-weaving process are at odds. So I had to change the solution described in Working With Gradle, Spring Aspects and Compile-time Weaving a bit.
The main idea is the same. We introduce new configurations:
configurations {
    ajc
    aspects
    compile {
        extendsFrom aspects
    }
}
and add required dependencies:
compile "org.aspectj:aspectjrt:$aspectjVersion"
compile "org.aspectj:aspectjweaver:$aspectjVersion"

ajc "org.aspectj:aspectjtools:$aspectjVersion"
aspects "org.springframework:spring-aspects:$springVersion"
Then we define a closure:
def aspectj = { destDir, aspectPath, inpath, classpath ->
    ant.taskdef(resource: "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties",
            classpath: configurations.ajc.asPath)

    ant.iajc(
            maxmem: "1024m", fork: "true", Xlint: "ignore",
            destDir: destDir,
            aspectPath: aspectPath,
            inpath: inpath,
            classpath: classpath,
            source: project.sourceCompatibility,
            target: project.targetCompatibility
    )
}
and change the standard Compile tasks:
compileJava {
    doLast {
        aspectj project.sourceSets.main.output.classesDir.absolutePath,
                configurations.aspects.asPath,
                project.sourceSets.main.output.classesDir.absolutePath,
                project.sourceSets.main.runtimeClasspath.asPath
    }
}

compileTestJava {
    dependsOn jar

    doLast {
        aspectj project.sourceSets.test.output.classesDir.absolutePath,
                configurations.aspects.asPath + jar.archivePath,
                project.sourceSets.test.output.classesDir.absolutePath,
                project.sourceSets.test.runtimeClasspath.asPath
    }
}
That's all. Lombok and AspectJ have been reconciled.

You can find a working example here.

Saturday, March 5, 2016

AOP and Constant Resolution

Our applications often depend on various reference information. For example, we can use the country list in our application. For the most part these lists have just two fields: identifier and name, but they can be more complex. Moreover, usually this data is stored in a database and rarely changed. To use these predefined items throughout the code we can define them as public static final fields of a class. For example,
public static final Country ALGERIA = new Country("DZ");
But this has a drawback. If we need the name or other fields besides the identifier alone we have to retrieve them from the database explicitly. Fortunately, we seldom need them. But on the other hand it leads to bugs that take work to be tracked down when we forget to do it.
To have all the fields always at hand we can use aspect-oriented programming (AOP). Any reference to a constant field will be substituted by its direct retrieval from the database.
First, we define a marker annotation to tag all the constants where the aspect should be applied.
package com.github.galleog.constants;

import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Annotation applied to {@code public static final} fields to resolve their values.
 *
 * @author Oleg Galkin
 */
@Documented
@Inherited
@Target({TYPE, FIELD})
@Retention(RUNTIME)
public @interface ResolveConstant {
}
Then we can define the aspect itself.
package com.github.galleog.constants;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.FieldSignature;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * Aspect to resolve {@code public static final} fields from the database automatically.
 *
 * @author Oleg Galkin
 */
@Aspect
public class ConstantResolverAspect {
    private FieldResolver resolver;

    /**
     * Sets {@link FieldResolver} to get field values.
     */
    public void setResolver(FieldResolver resolver) {
        this.resolver = resolver;
    }

    /**
     * Matches references to any {@code public static final} field annotated with {@link ResolveConstant}.
     */
    @Pointcut("get(@com.github.galleog.constants.ResolveConstant public static final * *.*)")
    void getPublicStaticFinalResolveConstantField() {
    }

    /**
     * Matches references to any {@code public static final} field belonging to a class
     * annotated with {@link ResolveConstant}.
     */
    @Pointcut("get(public static final * (@com.github.galleog.constants.ResolveConstant *).*)")
    void getAnyPublicStaticFinalFieldOfResolveContstantType() {
    }

    /**
     * Matches a call of {@code Field#get(Object)} for any {@code public static final} field
     * annotated with {@link ResolveConstant}.
     */
    @Pointcut("call(public Object java.lang.reflect.Field.get(Object)) && target(field) && if()")
    public static boolean getField(Field field) {
        int mod = field.getModifiers();
        return Modifier.isPublic(mod) && Modifier.isStatic(mod) && Modifier.isFinal(mod) &&
                (field.isAnnotationPresent(ResolveConstant.class) ||
                        field.getDeclaringClass().isAnnotationPresent(ResolveConstant.class));
    }

    /**
     * Resolves a directly referenced field.
     */
    @Around("getPublicStaticFinalResolveConstantField() || getAnyPublicStaticFinalFieldOfResolveContstantType()")
    public Object resolveDirectConstant(JoinPoint thisJointPoint) {
        FieldSignature signature = (FieldSignature) thisJointPoint.getStaticPart().getSignature();
        return resolver.resolve(signature.getField());
    }

    /**
     * Resolves a field referenced using Reflection.
     */
    @Around("getField(field) && !cflow(within(com.github.galleog.constants.ConstantResolverAspect))")
    public Object resolveReflectiveConstant(Field field) {
        return resolver.resolve(field);
    }
}
It depends on the following interface:
package com.github.galleog.constants;

import org.springframework.data.domain.Persistable;

import java.lang.reflect.Field;

/**
 * Interface to resolve values of fields.
 *
 * @author Oleg Galkin
 */
public interface FieldResolver {
    /**
     * Gets the field value.
     *
     * @param field the field to get the value for
     */
    public Persistable<?> resolve(Field field);
}
We can implement it using JPA.
package com.github.galleog.constants;

import org.apache.commons.lang3.Validate;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Persistable;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * Implementation of {@link FieldResolver} based on JPA.
 *
 * @author Oleg Galkin
 */
public class JpaFieldResolver implements FieldResolver {
    public static final String CACHE_NAME = "constants";

    @PersistenceContext
    private EntityManager em;

    @Override
    @Transactional(readOnly = true)
    @Cacheable(value = CACHE_NAME, key = "#field.declaringClass.name + '#' + #field.name")
    public Persistable<?> resolve(Field field) {
        int mod = field.getModifiers();
        Validate.isTrue(Modifier.isPublic(mod) && Modifier.isStatic(mod) && Modifier.isFinal(mod));
        try {
            Persistable<?> value = (Persistable<?>) field.get(null);
            return (Persistable<?>) em.find(field.getType(), value.getId());
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Could not access field " + field, e);
        }
    }
}
But remember to cache the retrieved constants, otherwise the database will be queried over and over again when we refer to them.
Unfortunately, this approach doesn't work when we use a constant field in, for example, Groovy. The point is that Groovy uses Java Reflection to get the constant value but if the aspect isn't applied to the Groovy library it won't work. The same is true for every library (expression languages and so on) that uses Reflection. But we can overcome the problem in Groovy if we annotate the method that refers to such a constant with @CompileStatic.

The code is available on GitHub.