10

My enums are stored as int in mongodb (from C# app). Now in Java, when I try to retrieve them, it throws an exception (it seems enum can be converted from string value only). Is there any way I can do it?

Also when I save some collections into mongodb (from Java), it converts enum values to string (not their value/cardinal). Is there any override available?

This can be achieved by writing mongodb-converter on class level but I don't want to write mondodb-converter for each class as these enums are in many different classes.

So do we have something on the field level?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
gsagrawal
  • 2,900
  • 4
  • 27
  • 27

3 Answers3

12

After a long digging in the spring-mongodb converter code, Ok i finished and now it's working :) here it is (if there is simpler solution i will be happy see as well, this is what i've done ) :

first define :

public interface IntEnumConvertable {
      public int getValue();    
}

and a simple enum that implements it :

public enum tester implements IntEnumConvertable{   
    vali(0),secondvali(1),thirdvali(5);

    private final int val;
    private tester(int num)
    {
        val = num;          
    }
    public int getValue(){
        return val;
    }
}

Ok, now you will now need 2 converters , one is simple , the other is more complex. the simple one (this simple baby is also handling the simple convert and returns a string when cast is not possible, that is great if you want to have enum stored as strings and for enum that are numbers to be stored as integers) :

public class IntegerEnumConverters {
    @WritingConverter
    public static class EnumToIntegerConverter implements Converter<Enum<?>, Object> {
        @Override
        public Object convert(Enum<?> source) {
            if(source instanceof IntEnumConvertable)
            {
                return ((IntEnumConvertable)(source)).getValue();
            }
            else
            {
                return source.name();
            }               
        }
    }   
 }

the more complex one , is actually a converter factory :

public class IntegerToEnumConverterFactory implements ConverterFactory<Integer, Enum> {
        @Override
        public <T extends Enum> Converter<Integer, T> getConverter(Class<T> targetType) {
            Class<?> enumType = targetType;
            while (enumType != null && !enumType.isEnum()) {
                enumType = enumType.getSuperclass();
            }
            if (enumType == null) {
                throw new IllegalArgumentException(
                        "The target type " + targetType.getName() + " does not refer to an enum");
            }
            return new IntegerToEnum(enumType);
        }
        @ReadingConverter
        public static class IntegerToEnum<T extends Enum>  implements Converter<Integer, Enum> {
            private final Class<T> enumType;

            public IntegerToEnum(Class<T> enumType) {
                this.enumType = enumType;
            }

            @Override
            public Enum convert(Integer source) {
                  for(T t : enumType.getEnumConstants()) {
                      if(t instanceof IntEnumConvertable)
                      {
                          if(((IntEnumConvertable)t).getValue() == source.intValue()) {
                                return t;
                            }                         
                      }                     
                    }
                    return null;   
            }
        }
}

and now for the hack part , i personnaly didnt find any "programmitacly" way to register a converter factory within a mongoConverter , so i digged in the code and with a little casting , here it is (put this 2 babies functions in your @Configuration class)

      @Bean
        public CustomConversions customConversions() {
            List<Converter<?, ?>> converters = new ArrayList<Converter<?, ?>>();
            converters.add(new IntegerEnumConverters.EnumToIntegerConverter());     
// this is a dummy registration , actually it's a work-around because
// spring-mongodb doesnt has the option to reg converter factory.
// so we reg the converter that our factory uses. 
converters.add(new IntegerToEnumConverterFactory.IntegerToEnum(null));      
            return new CustomConversions(converters);
        }

    @Bean
    public MappingMongoConverter mappingMongoConverter() throws Exception {
        MongoMappingContext mappingContext = new MongoMappingContext();
        mappingContext.setApplicationContext(appContext);
        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
        MappingMongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, mappingContext);        
        mongoConverter.setCustomConversions(customConversions());       
        ConversionService convService = mongoConverter.getConversionService();
        ((GenericConversionService)convService).addConverterFactory(new IntegerToEnumConverterFactory());                  
        mongoConverter.afterPropertiesSet();
        return mongoConverter;
    } 
Drew Wills
  • 8,408
  • 4
  • 29
  • 40
Robocide
  • 6,353
  • 4
  • 37
  • 41
  • 1
    Above receipt didn't work smoothly for me, as Spring Mongo started to apply the given convertor to convert all query values of type `Integer` to `Enum` in my case thus resulting `null`s for all values of Integer type. To fix the problem I had to place `@ReadingConverter` on convertor factory (hence remove it from `IntegerToEnum` convertor). – dma_k Apr 08 '20 at 19:29
1

You will need to implement your custom converters and register it with spring.

http://static.springsource.org/spring-data/data-mongo/docs/current/reference/html/#mongo.custom-converters

gkamal
  • 20,777
  • 4
  • 60
  • 57
  • that will work on class level .. i dont want that **already mentioned this in the question – gsagrawal Sep 12 '12 at 13:44
  • Didn't notice (format your question please, hard to read) - did you try it. I think it works for fields as well. – gkamal Sep 12 '12 at 14:45
  • 3
    The other option is to add another getter / setter for ints to your entity that does the conversion. The getter/setter for enum should be marked as @Transient. – gkamal Sep 12 '12 at 14:47
  • it takes first input as class . so it will not be generic ,for each enum/class i have to add one converter. Other option which u gave works well but this is kind of workaround . – gsagrawal Sep 13 '12 at 06:33
0

Isn't it easier to use plain constants rather than an enum...

int SOMETHING = 33;
int OTHER_THING = 55;

or

public class Role {
        public static final Stirng ROLE_USER = "ROLE_USER",
                                   ROLE_LOOSER = "ROLE_LOOSER";
}

String yourRole = Role.ROLE_LOOSER
Moritz
  • 1,954
  • 2
  • 18
  • 28