Saturday, April 16, 2016

JSON filters for Spring MVC

Spring MVC easily allows us to convert the responce body to JSON. It's great but unfortunately we usually need different JSON for different methods even for the same class. For instance, the class we have to serialize to JSON may have a lot of properties. We usually need just a small part of them when we list instances of the class in a grid. On the other hand, most of them are actually needed when we want to display the instance itself. If a class depends on other classes the problem becomes even complicated. Jackson JSON supports filters and views as a solution. Moreover, Spring 4.2 added support for Jackson's @JsonView annotation declared on a controller method. But I don't think Jackson's views are good for that. First of all you have to annotate methods of the class itself with @JsonView to filter its properties out. Then you have to define additional classes for the views. I think it'd be much easier just to list all the required properties for a controller method itself. So I added custom annotations for Spring MVC controllers:
/**
 * Annotation applied to a Spring MVC {@code @RequestMapping} or {@code @ExceptionHandler} method to filter out
 * its response body during JSON serialization.
 * <p/>
 * Example:
 * <pre>
 * public class PropSet extends PredefinedPropSet {
 *     public PropSet() {
 *         super("propA");
 *     }
 * }
 *
 * @JsonFilter(target = Bean.class, include = {"propB"}, propSets = PropSet.class)
 * public @RequestBody getBean() {
 *  ...
 * }
 * </pre>
 *
 * @author Oleg Galkin
 * @see com.fasterxml.jackson.databind.ObjectMapper#setFilterProvider(com.fasterxml.jackson.databind.ser.FilterProvider)
 * @see com.fasterxml.jackson.databind.ObjectMapper#setAnnotationIntrospector(com.fasterxml.jackson.databind.AnnotationIntrospector)
 */
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface JsonFilter {
    /**
     * Class the filter is used for.
     */
    Class<?> target();

    /**
     * Explicitly defined properties that should be kept when serializing the specified class to JSON.
     */
    String[] include() default {};

    /**
     * {@link PredefinedPropSet Property sets} that define the properties included in JSON.
     * Each property set class must have a default constructor.
     */
    Class<? extends PredefinedPropSet>[] propSets() default {};
}

/**
 * Declares several {@link JsonFilter} annotations for different classes on the same method.
 *
 * @author Oleg Galkin
 */
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface JsonFilters {
    /**
     * Defined filters.
     */
    JsonFilter[] value() default {};
}
The annotations can be used as follows:
@JsonFilters({
        @JsonFilter(target = User.class, propSets = {IdPropSet.class, UserPropSet.class}),
        @JsonFilter(target = Role.class, include = {"id", "name"})
})
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
@ResponseBody
public User getUser(long id) {
    ...
}
They allow listing properties included in JSON explicitly or using special property sets inherited from PredefinedPropSet.
To remove unnecessary properties we need Jackson's filter that only serializes the allowed object properties
public class ExceptPropertyFilter implements PropertyFilter {
    private final Map<Class<?>, Set<String>> filters = Maps.newHashMap();

    ...

    @Override
    public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider prov, PropertyWriter writer)
            throws Exception {
        if (include(pojo, writer)) {
            writer.serializeAsField(pojo, jgen, prov);
        } else if (!jgen.canOmitFields()) {
            writer.serializeAsOmittedField(pojo, jgen, prov);
        }
    }

   ...

    protected boolean include(Object object, PropertyWriter writer) {
        Validate.notNull(object);
        Set<String> properties = getProperties(object);
        return properties == null || properties.contains(writer.getName());
    }

    private Set<String> getProperties(Object object) {
        for (Class<?> cls = object.getClass(); cls != Object.class; cls = cls.getSuperclass()) {
            Set<String> fields = filters.get(cls);
            if (fields != null) {
                return fields;
            }
        }
        return null;
    }
}
The filter must be defined for all classes. Because Jackson uses string filter identifiers to find a specific filter, we have to define one and get it known to Jackson. To do it we can override the annotation introspector Jackson has:
public class JsonFilterAnnotationIntrospector extends JacksonAnnotationIntrospector {
    private final String filterId;

    ,,,

    @Override
    public Object findFilterId(Annotated ann) {
        Object id = super.findFilterId(ann);
        if (id == null) {
            JavaType javaType = TypeFactory.defaultInstance().constructType(ann.getRawType());
            if (!javaType.isContainerType()) {
                id = filterId;
            }
        }
        return id;
    }
}
The annotation introspector must be set to the object mapper Spring MVC MappingJackson2HttpMessageConverter will use. It can be done by overriding a WebMvcConfigurerAdapter method:
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
            .annotationIntrospector(new JsonFilterAnnotationIntrospector())
            .filters(new SimpleFilterProvider().setFailOnUnknownId(false))
            .build();
    converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
}
And finally we need to activate the filter when a method annotated with @JsonFilter is invoked by implementing Spring MVC ResponseBodyAdvice interface:
/**
 * {@link ResponseBodyAdvice} implementation that supports the {@link JsonFilter} annotation declared on a Spring MVC
 * {@code @RequestMapping} or {@code @ExceptionHandler} method.
 * <p/>
 * The created {@link ExceptPropertyFilter} is used within {@link MappingJackson2HttpMessageConverter} to serialize
 * the response body to JSON.
 *
 * @author Oleg Galkin
 * @see JsonFilter
 * @see ExceptPropertyFilter
 * @see MappingJackson2HttpMessageConverter
 */
@ControllerAdvice
public class JsonFilterResponseBodyAdvice extends AbstractMappingJacksonResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return super.supports(returnType, converterType) &&
                (returnType.getMethodAnnotation(JsonFilter.class) != null ||
                        returnType.getMethodAnnotation(JsonFilters.class) != null);
    }

    @Override
    protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType,
                                           MethodParameter returnType, ServerHttpRequest request,
                                           ServerHttpResponse response) {
        ExceptPropertyFilter filter;
        JsonFilter annotation = returnType.getMethodAnnotation(JsonFilter.class);
        if (annotation != null) {
            filter = new ExceptPropertyFilter(annotation);
        } else {
            filter = new ExceptPropertyFilter(returnType.getMethodAnnotation(JsonFilters.class).value());
        }

        SimpleFilterProvider filters = new SimpleFilterProvider();
        filters.addFilter(JsonFilterAnnotationIntrospector.DEFAULT_FILTER_ID, filter);
        bodyContainer.setFilters(filters);
    }
}
The code is available here.

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.