Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom argument resolver for @PathVariable

Scenario : My controller accepts a Long value for the id which is a Path Variable.

I need to pass a String which is an external reference to the id. So I need to resolve the string reference to its Long value.

Attempt: When the annotation @PathVariable is present, my custom argument resolver is not called as PathVariableMethodArgumentResolver is above than my custom resolver in the resolver list and it just supports all arguments with @PathVariable annotation

It works fine if I remove @PathVariable and add my own annotation. But then Swagger gets the id as a Request body parameter and produces this error:

TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body.

My custom resolver:

@Override
public boolean supportsParameter( MethodParameter methodParameter )
{
    return methodParameter.hasParameterAnnotation( ExternalRefParam.class );
}

@Override public Object resolveArgument( MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory ) throws Exception
{
    Map nameValueMap = (Map) nativeWebRequest.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, 0 );
    switch( methodParameter.getParameterName() )
    {
        case CART_ID:
            return resolveCartId( nameValueMap );
        case PRODUCT_KEY:
            return resolveProductKey( nameValueMap );
    }
    return -1L;
}

Controller Signature:

public ResponseEntity<Cart> readCart( 
                @ApiParam(value = "Cart ID", required = true) @ExternalRefParam Long cartId,  HttpServletRequest request )
like image 478
Ishadi Jayasinghe Avatar asked Apr 13 '26 15:04

Ishadi Jayasinghe


1 Answers

I had a similar issue where I wanted to add a custom argument resolver that would convert a path variable string value to uppercase. I solved it by creating a GenericConverter that converted a string => string for path variables annotated with a certain annotation.

The path variable annotation type was just a tagging annotation like this:

    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Uppercase {
        String value() default "";
    }

which was used on a rest controller mapping like this

    @PostMapping(value = "/clients/{clientId}/postalAddress")
    @ResponseStatus(HttpStatus.CREATED)
    public IdResponse create(
        @PathVariable("clientId") @Uppercase final String clientId,
        @RequestBody @NotNull @Valid final AddressRequest request) {...}

And then the Generic Converter was triggered to run on any String argument and the convert method checked that the argument was tagged with the Uppercase annotation to know if it should be uppercased. This also meant that Swagger API still reports the Path Variable as being from the path and properly extracts the path variable value, then runs the converter if it's annotated with Uppercase.

    public class CarPolicyIdAttributeConverter implements GenericConverter {

        @Override
        public Set<ConvertiblePair> getConvertibleTypes() {
            final ConvertiblePair[] pairs = new ConvertiblePair[] {
                new ConvertiblePair(String.class, String.class)
            };
            return ImmutableSet.copyOf(pairs);
        }

        @Override
        public Object convert(final Object source, final TypeDescriptor sourceType, final TypeDescriptor targetType) {
            if (targetType.getAnnotation(Uppercase.class) != null) {
                return ((String)source).toUppercase();
            }
            return source;
        }
    }
like image 84
Shane Rowatt Avatar answered Apr 16 '26 15:04

Shane Rowatt