7

I've got an endpoint:

/api/offers/search/findByType?type=X

where X should be an Integer value (an ordinal value of my OfferType instance), whereas Spring considers X a String and will be applying its StringToEnumConverterFactory with the StringToEnum convertor.

public interface OfferRepository extends PagingAndSortingRepository<Offer, Long> {

    List<Offer> findByType(@Param("type") OfferType type);

}

So I wrote a custom Converter<Integer, OfferType> which simply get a instance by the given ordinal number:

public class IntegerToOfferTypeConverter implements Converter<Integer, OfferType> {

    @Override
    public OfferType convert(Integer source) {
        return OfferType.class.getEnumConstants()[source];
    }

}

Then I registered it properly with a Configuration:

@EnableWebMvc
@Configuration
@RequiredArgsConstructor
public class GlobalMVCConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new IntegerToOfferTypeConverter());
    }

}

And I was expected that all requests to findByType?type=X will pass through my converter, but they do not.

Is any way to say that all enums defined as a request parameters have to be provided as an Integer? Furthermore, is any way to say it globally, not just for a specific enum?

EDIT: I've found IntegerToEnumConverterFactory in my classpath that does all I need. And it is registered with DefaultConversionService which is a default service for conversion. How can that be applied?

EDIT2: It's such a trivial thing, I was wondering if there is a property to turn enum conversion on.

EDIT3: I tried to write a Converter<String, OfferType> after I had got String from TypeDescriptor.forObject(value), it didn't help.

EDIT4: My problem was that I had placed custom converter registration into a MVC configuration (WebMvcConfigurerAdapter with addFormatters) instead of a REST Repositories one (RepositoryRestConfigurerAdapter with configureConversionService).

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • how does this question relate to spring data or JPA? – Stefan K. Mar 14 '17 at 18:51
  • 1
    Because he's using Spring Data JPA ? Andrew, does the entity you are querying have the @Enumerated annotation on the appropriate field ? As discussed here: http://stackoverflow.com/questions/17242408/spring-query-annotation-with-enum-parameter – PaulNUK Mar 17 '17 at 10:27
  • @PaulNUK, no, it doesn't – Andrew Tobilko Mar 17 '17 at 15:42
  • 1
    @StefanK. He's using the automated mapping of repositories to REST endpoints via Spring Data REST. It creates its own controllers. The OP should probably specify that in the question, but it has the right tag. – MartinTeeVarga Mar 20 '17 at 05:09

1 Answers1

6

Spring parses the query parameters as Strings. I believe it always uses Converter<String, ?> converters to convert from the query parameters to your repository methods parameters. It uses an enhanced converter service, since it registers its own converters such as Converter<Entity, Resource>.

Therefore you have to create a Converter<String, OfferType>, e.g.:

@Component
public class StringToOfferTypeConverter implements Converter<String, OfferType> {

    @Override
    public OfferType convert(String source) {
        return OfferType.class.getEnumConstants()[Integer.valueOf(source)];
    }
}

And then configure this converter to be used by the Spring Data REST, in a class extending RepositoryRestConfigurerAdapter:

@Configuration
public class ConverterConfiguration extends RepositoryRestConfigurerAdapter {

    @Autowired
    StringToOfferTypeConverter converter;

    @Override
    public void configureConversionService(ConfigurableConversionService conversionService) {
        conversionService.addConverter(converter);
        super.configureConversionService(conversionService);
    }
}

I tried to add this to the basic tutorial, added a simple enum to the Person class:

public enum OfferType {
    ONE, TWO;
}


@Entity
public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private OfferType type;


    public OfferType getType() {
        return type;
    }

    public void setType(OfferType type) {
        this.type = type;
    }
}

And when I call:

http://localhost:8080/people/search/findByType?type=1

I get the result without errors:

{
  "_embedded" : {
    "people" : [ ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/people/search/findByType?type=1"
    }
  }
}

To implement a global Enum converter, you have to create a factory and register it in the configuration using the method: conversionService.addConverterFactory(). The code below is a modified example from the documentation:

public class StringToEnumFactory implements ConverterFactory<String, Enum> {

    public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
        return new StringToEnum(targetType);
    }

    private final class StringToEnum<T extends Enum> implements Converter<String, T> {

        private Class<T> enumType;

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

        public T convert(String source) {
            Integer index = Integer.valueOf(source);
            return enumType.getEnumConstants()[index];
        }
    }
}
MartinTeeVarga
  • 10,478
  • 12
  • 61
  • 98