Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Imlpement AttributeConverter for Generic Class with @Converter

How do I implement AttributeConverter for Generics?

Something Like

class JSONConverter<T> implements AtttributeConverter<T,String>{
   //Here How do I get the generic class type with which I can convert a serialized object
}

call the converter in an entity class as

@Column
@Convert( converter = JSONConverter.class) //How do I pass the Generic here
private SomeClass sm;
like image 288
madhairsilence Avatar asked Feb 23 '17 10:02

madhairsilence


1 Answers

I used the following solution with Eclipselink and Java 8. One issue that wasn't immediately apparent is that the converter class must implement AttributeConverter directly (at least for it to work with Eclipselink)

Step 1. Define a generic interface to implement Object <-> String Json conversion. Because interfaces cannot contain properties I defined two methods getInstance() and getObjectMapper() to provide the conversion logic access to object instances that it requires at run time. Converter classes will need to provide implementations for these methods.

package au.com.sfamc.fusion.commons.jpa;
import java.io.IOException;
import javax.persistence.AttributeConverter;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public interface GenericJsonAttributeConverter<X> extends AttributeConverter<X, String> {

X getInstance();
ObjectMapper getObjectMapper();

@Override
default String convertToDatabaseColumn(X attribute) {
    String jsonString = "";
    try {

        // conversion of POJO to json
        if(attribute != null) {
            jsonString = getObjectMapper().writeValueAsString(attribute);
        } else {
            jsonString = "{}"; // empty object to protect against NullPointerExceptions
        }
    } catch (JsonProcessingException ex) {
    }

    return jsonString;
}

@Override
default X convertToEntityAttribute(String dbData) {
    X attribute = null;

    try {
        if(StringUtils.isNoneBlank(dbData)) {
            attribute = getObjectMapper().readValue(dbData, (Class<X>)getInstance().getClass());
        } else {
            attribute = getObjectMapper().readValue("{}", (Class<X>)getInstance().getClass());
        }            
    } catch (IOException ex) {
    }

    return attribute;
}

}

Step 2. The getObjectMapper() method implementation would be repeated every converter class so I introduced an abstract class that extends GenericJsonAttributeConverter to save having to implement this method in every converter class.

package au.com.sfamc.fusion.commons.jpa;

import com.fasterxml.jackson.databind.ObjectMapper;

public abstract class AbstractGenericJsonAttributeConverter<X> implements GenericJsonAttributeConverter<X> {

private static final ObjectMapper objectmapper = new ObjectMapper();

@Override
public ObjectMapper getObjectMapper() {
    return AbstractGenericJsonAttributeConverter.objectmapper;
}
}

Step 3. Create a concrete implementation of AbstractGenericJsonAttributeConverter for each class you want to to convert to and from Json, even though each class you want to convert will need its own concrete converter class at least you aren't duplicating the conversion code...

package au.com.sfamc.fusion.main.client;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

import au.com.sfamc.fusion.commons.jpa.AbstractGenericJsonAttributeConverter;

 @Converter
 public class ProjectMetricsReportJsonConverter extends AbstractGenericJsonAttributeConverter<ProjectMetricsReport> implements AttributeConverter<ProjectMetricsReport, String> {

private static final ProjectMetricsReport projectMetricsReport = new ProjectMetricsReport();

@Override
public ProjectMetricsReport getInstance() {
    return ProjectMetricsReportJsonConverter.projectMetricsReport;
}

}

Note: Now the trick to get this to work with Eclipselink is subtle but required. Along with extending AbstractGenericJsonAttributeConverter the concrete implementation must also make a direct implements reference to the `AttributeConverter' interface (a small price to pay to get generic conversion working)

like image 64
Edwin Quai Hoi Avatar answered Oct 14 '22 15:10

Edwin Quai Hoi