diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/MatrixVariableParameterProcessor.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/MatrixVariableParameterProcessor.java new file mode 100644 index 00000000..772c57d9 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/MatrixVariableParameterProcessor.java @@ -0,0 +1,83 @@ +/* + * Copyright 2013-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.openfeign.annotation; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.stream.Collectors; + +import feign.MethodMetadata; + +import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; +import org.springframework.web.bind.annotation.MatrixVariable; + +import static feign.Util.checkState; +import static feign.Util.emptyToNull; + +/** + * {@link MatrixVariable} annotation processor. + * + * Can expand maps or single objects. Values are assigned from the objects {@code toString()} + * method. + * + * @author Matt King + * @see AnnotatedParameterProcessor + */ +public class MatrixVariableParameterProcessor implements AnnotatedParameterProcessor { + + private static final Class ANNOTATION = MatrixVariable.class; + + @Override + public Class getAnnotationType() { + return ANNOTATION; + } + + @Override + public boolean processArgument(AnnotatedParameterContext context, + Annotation annotation, Method method) { + int parameterIndex = context.getParameterIndex(); + Class parameterType = method.getParameterTypes()[parameterIndex]; + MethodMetadata data = context.getMethodMetadata(); + String name = ANNOTATION.cast(annotation).value(); + + checkState(emptyToNull(name) != null, + "MatrixVariable annotation was empty on param %s.", + context.getParameterIndex()); + + context.setParameterName(name); + + if (Map.class.isAssignableFrom(parameterType)) { + data.indexToExpander().put(parameterIndex, this::expandMap); + } + else { + data.indexToExpander().put(parameterIndex, + object -> ";" + name + "=" + object.toString()); + } + + return true; + } + + private String expandMap(Object object) { + Map paramMap = (Map) object; + + return paramMap.keySet().stream() + .map(key -> ";" + key + "=" + paramMap.get(key).toString()) + .collect(Collectors.joining()); + } + +} diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java index 7e1168c8..0e9f5569 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java @@ -36,6 +36,7 @@ import feign.Param; import feign.Request; import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; +import org.springframework.cloud.openfeign.annotation.MatrixVariableParameterProcessor; import org.springframework.cloud.openfeign.annotation.PathVariableParameterProcessor; import org.springframework.cloud.openfeign.annotation.QueryMapParameterProcessor; import org.springframework.cloud.openfeign.annotation.RequestHeaderParameterProcessor; @@ -355,6 +356,7 @@ public class SpringMvcContract extends Contract.BaseContract List annotatedArgumentResolvers = new ArrayList<>(); + annotatedArgumentResolvers.add(new MatrixVariableParameterProcessor()); annotatedArgumentResolvers.add(new PathVariableParameterProcessor()); annotatedArgumentResolvers.add(new RequestParamParameterProcessor()); annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor()); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java index fabae102..39c8a215 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java @@ -23,6 +23,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -45,6 +46,7 @@ import org.springframework.util.MultiValueMap; import org.springframework.util.ReflectionUtils; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.MatrixVariable; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -523,6 +525,35 @@ public class SpringMvcContractTests { this.contract.parseAndValidateMetadata(method.getDeclaringClass(), method); } + @Test + public void testMatrixVariable_MapParam() throws Exception { + Method method = TestTemplate_MatrixVariable.class + .getDeclaredMethod("matrixVariable", Map.class); + MethodMetadata data = this.contract + .parseAndValidateMetadata(method.getDeclaringClass(), method); + + Map testMap = new HashMap<>(); + testMap.put("param", "value"); + + assertThat(data.template().method()).isEqualTo("GET"); + assertThat(data.template().url()).isEqualTo("/matrixVariable/{params}"); + assertThat(";param=value") + .isEqualTo(data.indexToExpander().get(0).expand(testMap)); + } + + @Test + public void testMatrixVariable_ObjectParam() throws Exception { + Method method = TestTemplate_MatrixVariable.class + .getDeclaredMethod("matrixVariableObject", Object.class); + MethodMetadata data = this.contract + .parseAndValidateMetadata(method.getDeclaringClass(), method); + + assertThat(data.template().method()).isEqualTo("GET"); + assertThat(data.template().url()).isEqualTo("/matrixVariableObject/{param}"); + assertThat(";param=value") + .isEqualTo(data.indexToExpander().get(0).expand("value")); + } + @Test public void testAddingTemplatedParameterWithTheSameKey() throws NoSuchMethodException { @@ -639,6 +670,16 @@ public class SpringMvcContractTests { } + public interface TestTemplate_MatrixVariable { + + @RequestMapping(path = "/matrixVariable/{params}") + String matrixVariable(@MatrixVariable("params") Map params); + + @RequestMapping(path = "/matrixVariableObject/{param}") + String matrixVariableObject(@MatrixVariable("param") Object object); + + } + @JsonAutoDetect @RequestMapping("/advanced") public interface TestTemplate_Advanced {