Creating configurable Hybris converters using annotations

The most of developers are aware about converter pattern. The main goal of converter is to convert a source object of type S to a target of type T. We usually need it to convert a model object to the DTO. Despite the fact that idea is very simple there are even many frameworks which help you to do that e.g. Dozer or Mapstruct. Hybris also has it own way to convert data. Basically it provides Converter and Populator interfaces. Converter is used to instantiate an instance of target type and then converter delegates work to a populator which is used to fill the target instance.

interface Converter<S,T> {
   T convert(S source);
}
interface Populator<S,T> {
   void populate(S source, T target);
}

The idea of hybris way is to have a single implementation of converter and reuse it all over the codebase. This implementation should accept target type and populators as parameters and any developer should only write code for populator and create converters only using spring configuration. Sometimes it might be annoying…

Once when I finished writing a populator and I thougt “Why should I declare 10 more lines in spring.xml just to create converter? Why not to do this declaratively?”. At that point I decided to make my own declarative implementation.

My idea was following:

  1. create @Converter annotation
  2. implement BeanDefinitionRegistryPostProcessor to dynamically add converter bean definitions in the context
  3. all added bean definitions for converters are instantiated by spring automatically.
  4. profit

Step 1 – Create @Converter annotation

We need to have a Runtime available annotation with value field for converter name

@Retention(RetentionPolicy.RUNTIME)
public @interface Converter {
 String value();
}

Step 2 – Implement BeanDefinitionRegistryPostProcessor

Here the most interesting part goes. Things we need to do in this class are:

  1. Scan all bean definitions that implement Populator interface
  2. Filter only those classes that have @Converter annotation
  3. Group filtered bean definitions by converter name
  4. Create a converter for a group of populators using converter name as bean id, type of target parameter in the populator as a target class, and grouped populator as populators for converter.

Lets have an example:

@Component
@Converter("productConverter")
public class BasicProductPopulator implements Populator<Product, ProductData> {

    public void populate(Product product, ProductData productData) {
        productData.setName(product.getName());
        productData.setDescription(product.getDescription());
    }

}
@Component
@Converter("productConverter")
public class MoneyProductPopulator implements Populator<Product, ProductData> {

    public void populate(Product product, ProductData productData) {
        productData.setPrice(formatPrice(product.getPrice()));
        productData.setTax(formatTax(product.getTax()));
    }

    private String formatPrice(Price price) {
        return price.getValue() + price.getCurrencySymbol();
    }

    private String formatTax(Tax tax) {
        return tax.getVatRate() + " " + tax.getCode();
    }
}

So we have 2 populators MoneyProductPopulator and BasicProductPopulator. Both of them implement Populator<Product, ProductData>. After our custom BeanDefinitionRegistryPostProcessor scanned and filtered all beans definitions it would group MoneyProductPopulator and BasicProductPopulator into list, it will take
ProductData as a target class for converter and create a bean definition for them in spring context. So that you could inject the converter in your code.

@Resource
Converter<Product, ProductData> productConverter;

It is the source code of ConverterResolverBeanDefinitionRegistryPostProcessor:

package example;

import example.Populator;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class ConverterResolverBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    public static final String CONVERTER_CLASS = "com.copmany.DEFAUL_IMPLEMENTATION_OF_CONVERTER";
    public static final String TARGET_CLASS = "targetClass";
    public static final String POPULATORS = "populators";
    public static final String POPULATE_METHOD = "populate";
    public static final int TARGET_PARAM = 1;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        Map<String, List<BeanDefinition>> converterNameToPopulators =
                Stream.of(beanDefinitionRegistry.getBeanDefinitionNames())
                        .map(beanDefinitionRegistry::getBeanDefinition)
                        .filter(this::doesBeanImplementPopulatorInterface)
                        .filter(this::doesBeanClassHaveConverterAnnotation)
                        .collect(Collectors.groupingBy(this::getConverterName));

        converterNameToPopulators.forEach((converterName, populators) ->
                createAndRegisterConverter(converterName, populators, beanDefinitionRegistry));
    }

    private boolean doesBeanImplementPopulatorInterface(BeanDefinition beanDefinition) {
        Class<?> beanClass = getBeanClass(beanDefinition);
        return Stream.of(beanClass.getInterfaces()).anyMatch(Populator.class::equals);
    }

    private boolean doesBeanClassHaveConverterAnnotation(BeanDefinition beanDefinition) {
        Class<?> beanClass = getBeanClass(beanDefinition);
        return Stream.of(beanClass.getAnnotations()).map(Annotation::annotationType).anyMatch(Converter.class::equals);
    }

    private String getConverterName(BeanDefinition populatorBeanDefinition) {
        Converter converterAnnotation = getBeanClass(populatorBeanDefinition).getAnnotation(Converter.class);
        return converterAnnotation.value();
    }

    private void createAndRegisterConverter(String converterName, List<BeanDefinition> populators, BeanDefinitionRegistry beanDefinitionRegistry) {
        BeanDefinition converter = createConverterBeanDefinition(populators);
        beanDefinitionRegistry.registerBeanDefinition(converterName, converter);
    }

    private BeanDefinition createConverterBeanDefinition(List<BeanDefinition> populators) {
        GenericBeanDefinition converterBeanDefinition = new GenericBeanDefinition();
        converterBeanDefinition.setBeanClassName(CONVERTER_CLASS);
        converterBeanDefinition.setPropertyValues(createBeanProperties(populators));
        return converterBeanDefinition;
    }

    private MutablePropertyValues createBeanProperties(List<BeanDefinition> populators) {
        MutablePropertyValues properties = new MutablePropertyValues();
        properties.addPropertyValue(POPULATORS, createPopulatorsList(populators));
        properties.addPropertyValue(TARGET_CLASS, getPopulatorTargetClass(populators.get(0)));
        return properties;
    }

    private ManagedList<BeanDefinition> createPopulatorsList(List<BeanDefinition> populators) {
        ManagedList<BeanDefinition> populatorReferences = new ManagedList<>();
        populatorReferences.setElementTypeName(Populator.class.getName());
        populatorReferences.addAll(populators);
        return populatorReferences;
    }

    private Class<?> getPopulatorTargetClass(BeanDefinition populatorBeanDefinition) {
        Method[] declaredMethods = getBeanClass(populatorBeanDefinition).getDeclaredMethods();
        return Stream.of(declaredMethods)
                .filter(method -> POPULATE_METHOD.equals(method.getName()))
                .findFirst()
                .map(Method::getParameters)
                .map(parameters -> parameters[TARGET_PARAM])
                .map(Parameter::getType)
                .get();
    }

    private Class<?> getBeanClass(BeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        return toClass(className);
    }

    private  Class<?> toClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // no operation
    }

}

So basically that is all for the article. Thanks for reading.

Advertisements

Author: nikitapavlenko

I am Software Engineer from Kharkiv, Ukraine. I develop e-commerce web applications using Hybris platfrom mostly using Java.

One thought on “Creating configurable Hybris converters using annotations”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s