java uses morphia to access enumeration as value

Keywords: Java github MongoDB Apache

Source code

https://github.com/zhongchengyi/zhongcy.demos/tree/master/mongo-morphia-demo

Preface

Morphia is a library that java uses orm to operate mongodb. But by default, when using morphia to access enum, it is accessed by name. We need to access enum by value.

As shown in the figure: the schoolClassLevel1 field is accessed by enum name by default, and the schoolClassLevel is what we want (accessed by value).

Core code

Initialize morphia

Morphia morphia = new Morphia();
            try {
                Converters converters = morphia.getMapper().getConverters();
                Method getEncoder = Converters.class.getDeclaredMethod("getEncoder", Class.class);
                getEncoder.setAccessible(true);
                TypeConverter enco = ((TypeConverter) getEncoder.invoke(converters, SchoolClassLevel.class));
                converters.removeConverter(enco);
                converters.addConverter(new EnumOrginalConverter());
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

 

Where, EnumOrginalConverter.java

package zhongcy.demos.converter;

import dev.morphia.converters.SimpleValueConverter;
import dev.morphia.converters.TypeConverter;
import zhongcy.demos.util.EnumOriginalProvider;
import zhongcy.demos.util.EnumUtil;


public class EnumOrginalConverter extends TypeConverter implements SimpleValueConverter {

    @Override
    @SuppressWarnings({"unchecked", "deprecation"})
    public Object decode(final Class targetClass, final Object fromDBObject, final dev.morphia.mapping.MappedField optionalExtraInfo) {
        if (fromDBObject == null) {
            return null;
        }

        if (hasEnumOriginalProvider(targetClass)) {
            return EnumUtil.getEnumObject(Long.parseLong(fromDBObject.toString()), targetClass);
        }

        return Enum.valueOf(targetClass, fromDBObject.toString());
    }

    @Override
    @SuppressWarnings({"unchecked", "deprecation"})
    public Object encode(final Object value, final dev.morphia.mapping.MappedField optionalExtraInfo) {
        if (value == null) {
            return null;
        }

        if (hasEnumOriginalProvider(value.getClass())) {
            return ((EnumOriginalProvider) value).getIdx();
        }

        return getName(((Enum) value));
    }

    private boolean hasEnumOriginalProvider(Class clzz) {
        Class<?>[] interfaces = clzz.getInterfaces();
        if (interfaces.length < 1) {
            return false;
        }
        if (interfaces.length == 1) {
            return interfaces[0] == EnumOriginalProvider.class;
        } else {
            for (Class<?> it : interfaces) {
                if (it == EnumOriginalProvider.class) {
                    return true;
                }
            }
            return false;
        }
} @Override @SuppressWarnings({
"unchecked", "deprecation"}) protected boolean isSupported(final Class c, final dev.morphia.mapping.MappedField optionalExtraInfo) { return c.isEnum(); } private <T extends Enum> String getName(final T value) { return value.name(); } }
EnumOriginalProvider.java
package zhongcy.demos.util;

/**
 * enum Raw data supply of
 */
public interface EnumOriginalProvider {

    default String getName() {
        return null;
    }

    long getIdx();
}

EnumUtil.java

package zhongcy.demos.util;

import org.apache.calcite.linq4j.Linq4j;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class EnumUtil {

    private static EnumUtil instance = new EnumUtil();

    Impl impl;

    EnumUtil() {
        impl = new Impl();
    }

    /**
     * * Get value return enumeration object
     * * @param value name Or index
     * * @param clazz Enumeration type
     * *
     */
    public static <T extends EnumOriginalProvider> T getEnumObject(long idx, Class<T> clazz) {
        return instance.impl.getEnumObject(idx, clazz);
    }



    public static <T extends EnumOriginalProvider> T getEnumObject(String name, Class<T> clazz) {
        return instance.impl.getEnumObject(name, clazz);
    }

    private class Impl {

        private Map<Class, EnumFeature[]> enumMap;

        public Impl() {
            enumMap = new HashMap<>();
        }

        public <T extends EnumOriginalProvider> T getEnumObject(long value, Class<T> clazz) {
            if (!enumMap.containsKey(clazz)) {
                enumMap.put(clazz, createEnumFeatures(clazz));
            }

            try {
                EnumFeature first = Linq4j.asEnumerable(enumMap.get(clazz))
                        .firstOrDefault(f -> value == f.getIndex());
                if (first != null) {
                    return (T) first.getEnumValue();
                }
            } catch (Exception e) {
            }
            return null;
        }



        public <T extends EnumOriginalProvider> T getEnumObject(String value, Class<T> clazz) {
            if (!enumMap.containsKey(clazz)) {
                enumMap.put(clazz, createEnumFeatures(clazz));
            }

            try {
                EnumFeature first = Linq4j.asEnumerable(enumMap.get(clazz))
                        .firstOrDefault(f -> value.equals(f.getName()) || f.getEnumValue().toString().equals(value));
                if (first != null) {
                    return (T) first.getEnumValue();
                }
            } catch (Exception e) {
            }
            return null;
        }

        @SuppressWarnings("JavaReflectionInvocation")
        private <T extends EnumOriginalProvider> EnumFeature[] createEnumFeatures(Class<T> cls) {
            Method method = null;
            try {
                method = cls.getMethod("values");
                return Linq4j.asEnumerable((EnumOriginalProvider[]) method.invoke(null, (Object[]) null))
                        .select(s -> new EnumFeature(s, s.getName(), s.getIdx())).toList().toArray(new EnumFeature[0]);
            } catch (Exception e) {
                e.printStackTrace();
                return new EnumFeature[0];
            }
        }
    }

    private class EnumFeature {

        Object enumValue;

        String name;

        long index;

        public EnumFeature(Object enumValue, String name, long index) {
            this.enumValue = enumValue;
            this.name = name;
            this.index = index;
        }

        public Object getEnumValue() {
            return enumValue;
        }

        public String getName() {
            return name;
        }

        public long getIndex() {
            return index;
        }
    }
}

A simple analysis of morphia

Trace all the way through the save method of dev.morphia.DataStoreImpl

 

 

 

At this point, look at the implementation of the TypeConverter,

 

Find the enum converter. You can see that morphia uses enum.getname when encoding enum. We will try to replace the converter

package dev.morphia.converters;


import dev.morphia.mapping.MappedField;


/**
 * @author Uwe Schaefer, (us@thomas-daily.de)
 * @author scotthernandez
 */
public class EnumConverter extends TypeConverter implements SimpleValueConverter {

    @Override
    @SuppressWarnings("unchecked")
    public Object decode(final Class targetClass, final Object fromDBObject, final MappedField optionalExtraInfo) {
        if (fromDBObject == null) {
            return null;
        }
        return Enum.valueOf(targetClass, fromDBObject.toString());
    }

    @Override
    public Object encode(final Object value, final MappedField optionalExtraInfo) {
        if (value == null) {
            return null;
        }

        return getName((Enum) value);
    }

    @Override
    protected boolean isSupported(final Class c, final MappedField optionalExtraInfo) {
        return c.isEnum();
    }

    private <T extends Enum> String getName(final T value) {
        return value.name();
    }
}

Converter replacement

Check the interface of morphia and get the Converters as follows

Converters converters = morphia.getMapper().getConverters();

However, in the interfaces of Converters, the related methods are not public

 

 

Check the initialization of Converters. It is also found that the initialization path is very long and a custom Converter subclass is not provided. Therefore, the reflection method is adopted to find enumConvert and replace it with Converter that supports return value.

Namely:

                Converters converters = morphia.getMapper().getConverters();
                Method getEncoder = Converters.class.getDeclaredMethod("getEncoder", Class.class);
                getEncoder.setAccessible(true);
                TypeConverter enco = ((TypeConverter) getEncoder.invoke(converters, SchoolClassLevel.class));
                converters.removeConverter(enco);
                converters.addConverter(new EnumOrginalConverter());

Implementation of EnumOrginalConverter

The implementation is relatively simple. By judging whether enum has a specified interface (EnumOriginalProvider), if so, the encode method returns a value.

Code seeAbove (EnumOrginalConverter)

The source code defines two enumerations:

 

The final database SchoolClassLevel is the value, SchoolClassLevel1 is the name

 

Other

Posted by nanny79 on Tue, 31 Dec 2019 06:59:46 -0800