Converter from @PathVariable DomainObject to String? (using ControllerLinkBuilder.methodOn)

I am trying to call Spring ControllerLinkBuilder.methodOn() with a non-String type that always fails. And I do not know what type of Converter use and where to register it.

Here is my controller:

 @RestController @RequestMapping("/companies") class CompanyController { @RequestMapping(value="/{c}", method=RequestMethod.GET) void getIt(@PathVariable Company c) { System.out.println(c); Link link = linkTo(methodOn(getClass()).getIt(c)); } } 

System.out.println(c) works well. My Company Domain object is retrieved from the database. (I am using DomainClassConverter )

But another way doesn't work: ConverterNotFoundException: No converter found capable of converting from type @PathVariable Company to type String

I only need Converter<Company, String> ? And where should I register it? I tried something in the addFormatters(FormatterRegistry registry) WebMvcConfigurationSupport , but it just displayed the same error. But I'm not sure what I tried ...

+6
source share
4 answers

Found a "solution". It requires a lot of copies and pasting from Spring classes, but at least it works!

Basically I had to copy org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor and change two lines:

 class AnnotatedParametersParameterAccessor { ... static class BoundMethodParameter { // OLD: (with this one you can't call addConverter()) // private static final ConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService(); // NEW: private static final FormattingConversionService CONVERSION_SERVICE = new DefaultFormattingConversionService(); ... public BoundMethodParameter(MethodParameter parameter, Object value, AnnotationAttribute attribute) { ... // ADD: CONVERSION_SERVICE.addConverter(new MyNewConverter()); } ... } 

This class is used by ControllerLinkBuilderFactory . So I had to copy and paste this too.

And this method is used by ControllerLinkBuilder . Also copy and paste.

My Converter just does myDomainObject.getId().toString() :

 public class MyNewConverter implements Converter<Company, String> { @Override public String convert(Company source) { return source.getId().toString(); } } 

Now you can use the copied & Paste ControllerLinkBuilder inside the controller, and it works as expected!

0
source

I had the same problem, this is a mistake . If you do not want to copy and paste to each controller, you can try something similar in your WebMvcConfigurationSupport . This works for me.

 @Override public void addFormatters(final FormatterRegistry registry) { super.addFormatters(registry); try { Class<?> clazz = Class.forName("org.springframework.hateoas.mvc.AnnotatedParametersParameterAccessor$BoundMethodParameter"); Field field = clazz.getDeclaredField("CONVERSION_SERVICE"); field.setAccessible(true); DefaultFormattingConversionService service = (DefaultFormattingConversionService) field.get(null); for (Converter<?, ?> converter : beanFactory.getBeansOfType(Converter.class).values()) { service.addConverter(converter); } } catch (Exception ex) { throw new RuntimeException(ex); } } 
+5
source

I developed a framework for rendering links in spring hateoas and supports annotated parameters ( @PathVariable and @RequestParam ) and arbitrary parameter types.

To execute these arbitrary types, you need to create a spring bean that implements the com.github.osvaldopina.linkbuilder.argumentresolver.ArgumentResolver interface.

The interface has 3 methods:

  • public boolean resolveFor(MethodParameter methodParameter)

Used to determine if ArgumentResolver can be used to work with methodParameter . For instance:

 public boolean resolveFor(MethodParameter methodParameter) { return UserDefinedType.class.isAssignableFrom(methodParameter.getParameterType()); } 

Specifies that this ArgumentResover will be used for UserDefinedType .

  1. public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter)

Used to include in the uriTemplate associated with the method the corresponding parts of the template. For instance:

 @Override public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter) { uriTemplateAugmenter.addToQuery("value1"); uriTemplateAugmenter.addToQuery("value2"); } 

Adds 2 query patterns (value1 and value2) to the uri pattern.

  1. public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames)

Sets the values ​​for template variables in the template. For instance:

 @Override public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames) { if (parameter != null && ((UserDefinedType) parameter).getValue1() != null) { template.set("value1", ((UserDefinedType) parameter).getValue1()); } else { template.set("value1", "null-value"); } if (parameter != null && ((UserDefinedType) parameter).getValue2() != null) { template.set("value2", ((UserDefinedType) parameter).getValue2()); } else { template.set("value2", "null-value"); } } 

receives an instance of UserDefinedType and uses it to set the template variables value1 and value2 defined in the augmentTemplate method.

A ArgumentResolver full example:

 @Component public class UserDefinedTypeArgumentResolver implements ArgumentResolver { @Override public boolean resolveFor(MethodParameter methodParameter) { return UserDefinedType.class.isAssignableFrom(methodParameter.getParameterType()); } @Override public void augmentTemplate(UriTemplateAugmenter uriTemplateAugmenter, MethodParameter methodParameter) { uriTemplateAugmenter.addToQuery("value1"); uriTemplateAugmenter.addToQuery("value2"); } @Override public void setTemplateVariables(UriTemplate template, MethodParameter methodParameter, Object parameter, List<String> templatedParamNames) { if (parameter != null && ((UserDefinedType) parameter).getValue1() != null) { template.set("value1", ((UserDefinedType) parameter).getValue1()); } else { template.set("value1", "null-value"); } if (parameter != null && ((UserDefinedType) parameter).getValue2() != null) { template.set("value2", ((UserDefinedType) parameter).getValue2()); } else { template.set("value2", "null-value"); } } } 

and for the following link builder:

  linksBuilder.link() .withRel("user-type") .fromControllerCall(RootRestController.class) .queryParameterForUserDefinedType(new UserDefinedType("v1", "v2")); 

the following method:

 @RequestMapping("/user-defined-type") @EnableSelfFromCurrentCall public void queryParameterForUserDefinedType(UserDefinedType userDefinedType) { } 

will create the following link:

 { ... "_links": { "user-type": { "href": "http://localhost:8080/user-defined-type?value1=v1&value2=v2" } ... } 

}

0
source

full configuration in spring boot. just like Franco Gotusso’s answer, just provide more details. `` ``

/ ** * This configuration file should fix the spring Hateoas error. * check https://github.com/spring-projects/spring-hateoas/issues/118 . * /

@Component The public class MvcConfig extends WebMvcConfigurerAdapter {

 @Autowired private ApplicationContext applicationContext; @Override public void addFormatters(final FormatterRegistry registry) { super.addFormatters(registry); try { Class<?> clazz = Class.forName("org.springframework.hateoas.mvc." + "AnnotatedParametersParameterAccessor$BoundMethodParameter"); Field field = clazz.getDeclaredField("CONVERSION_SERVICE"); field.setAccessible(true); DefaultFormattingConversionService service = (DefaultFormattingConversionService) field.get(null); for (Formatter<?> formatter : applicationContext .getBeansOfType(Formatter.class).values()) { service.addFormatter(formatter); } for (Converter<?, ?> converter : applicationContext .getBeansOfType(Converter.class).values()) { service.addConverter(converter); } } catch (Exception ex) { throw new RuntimeException(ex); } } 

}

`` ``

0
source

All Articles