Accepting a Spring Data REST URI in custom control

2019-03-23 09:32发布

问题:

I have a Spring Data Rest webmvc application that I'd like to add some custom functionality to for batch operations.

I've created a controller, and blended it into the uri namespace, but I'd like for it to be able to accept URI's like the custom /search queries do, rather than simply an ID.

I have tried registering a custom <String, Long> converter (my entity has a Long ID type, but that seems to get ignored. Is there any way to configure my controller such that it adopts that behavior from the auto-implemented SDR controllers?

Even if there is some sort of method I can call that will auto-resolve a URI to an entity, that would work just as well (as I can then simply accept a String in my controller)

Here's where I'm at.

@Configuration
public class CustomWebConfiguration extends WebMvcConfigurationSupport {

    //irrelevant code omitted

    @Bean
    public DomainClassConverter<?> domainClassConverter() {
        DomainClassConverter<FormattingConversionService> dc = new DomainClassConverter<FormattingConversionService>(mvcConversionService());
        return dc;
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
          registry.addConverter(String.class, Long.class, testConverter());
    }

    @Bean 
    Converter<String, Long> testConverter() {
        return new Converter<String, Long>() {

            @Override
            public Long convert(String source) {
                //this code does _not_ get run at any point
                if (source.indexOf('/') == -1) { return Long.parseLong(source); }

                source = source.substring(source.lastIndexOf('/') + 1);
                Long id = Long.parseLong(source);

                return id;
            }   
        };
    }
}

SDR Config

@Configuration
@EnableHypermediaSupport(type = { HypermediaType.HAL })
public class CustomRestConfiguration extends RepositoryRestMvcConfiguration {

    @Override
    public RepositoryRestConfiguration config() {
      RepositoryRestConfiguration config = super.config();
      config.setBasePath("/api");
      config.exposeIdsFor(ApplicationMembership.class);
      return config;
    }


}

And my (contrived) controller:

ApplicationType is one of my entities that are correctly managed by SDR/repository magic

@BasePathAwareController
@RepositoryRestController
@RequestMapping("applications/special")
public class ApplicationExtensionController {
    @RequestMapping("a")
    public ResponseEntity<?> reply(@RequestParam("type") ApplicationType type) {
        return new ResponseEntity<String>(type.getIcon(), HttpStatus.OK);
    }
}

I've looked around quite a bit but can't quite manage to make anything work. When I create a <String, ApplicationType> converter that utilizes the repository, it also does not get called, as the DomainClassConverter just calls its underlying <String, Long> converter (which obviously fails, as it cannot correctly parse out types/1 into a long.

Appreciate the help!

Forgot to mention

  • Spring Data Rest 2.4.0
  • Spring HATEOAS 0.19.0
  • Spring 4.2.1

Using JPA repositories

回答1:

As it turns out, I was on the right track with adding a converter, unfortunately I was doing it in the wrong configuration method.

I was able to get the desired functionality by moving my testConverter() bean to the RepositoryRestMvcConfiguration extension config class and then adding

@Override
public void configureConversionService(ConfigurableConversionService service) {
    service.addConverter(testConverter());
}

And working as intended. I feel a bit silly now for throwing that in the wrong spot in the first place, but hopefully this will help someone else out!



回答2:

I am posting an answer based on my last comment.

Apparently the logic you require -- autoconverting URI from @RequestParam to a repository-managed entity -- is implemented in a few private methods of RepositorySearchController (see executeQueryMethod and prepareUris), so there's no easy way to get it in custom controllers.

You can try creating your own argument resolver with Spring HATEOAS. Look into how PersistentEntityResourceHandlerMethodArgumentResolver resolver is implemented. It resolved an entity based on its @BackendId.