diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/CookieValueParameterProcessor.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/CookieValueParameterProcessor.java new file mode 100644 index 00000000..f18f27c2 --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/annotation/CookieValueParameterProcessor.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013-2021 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.Arrays; + +import feign.MethodMetadata; + +import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.CookieValue; + +import static feign.Util.checkState; +import static feign.Util.emptyToNull; + +/** + * @{link CookieValue} annotation processor. + * + * @author Gong Yi + * + */ +public class CookieValueParameterProcessor implements AnnotatedParameterProcessor { + + private static final Class ANNOTATION = CookieValue.class; + + @Override + public Class getAnnotationType() { + return ANNOTATION; + } + + @Override + public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) { + int parameterIndex = context.getParameterIndex(); + MethodMetadata data = context.getMethodMetadata(); + CookieValue cookie = ANNOTATION.cast(annotation); + String name = cookie.value().trim(); + checkState(emptyToNull(name) != null, "Cookie.name() was empty on parameter %s", parameterIndex); + context.setParameterName(name); + String cookieExpression = data.template().headers().getOrDefault(HttpHeaders.COOKIE, Arrays.asList("")).stream() + .findFirst().orElse(""); + if (cookieExpression.length() == 0) { + cookieExpression = String.format("%s={%s}", name, name); + } + else { + cookieExpression += String.format("; %s={%s}", name, name); + } + data.template().removeHeader(HttpHeaders.COOKIE); + data.template().header(HttpHeaders.COOKIE, cookieExpression); + return true; + } + +} 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 a3f2c8b5..504c9262 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 @@ -38,6 +38,7 @@ import feign.Request; import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; import org.springframework.cloud.openfeign.CollectionFormat; +import org.springframework.cloud.openfeign.annotation.CookieValueParameterProcessor; import org.springframework.cloud.openfeign.annotation.MatrixVariableParameterProcessor; import org.springframework.cloud.openfeign.annotation.PathVariableParameterProcessor; import org.springframework.cloud.openfeign.annotation.QueryMapParameterProcessor; @@ -360,6 +361,7 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource annotatedArgumentResolvers.add(new RequestHeaderParameterProcessor()); annotatedArgumentResolvers.add(new QueryMapParameterProcessor()); annotatedArgumentResolvers.add(new RequestPartParameterProcessor()); + annotatedArgumentResolvers.add(new CookieValueParameterProcessor()); return annotatedArgumentResolvers; } 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 69b25f6f..a034481a 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 @@ -45,6 +45,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.MultiValueMap; import org.springframework.util.ReflectionUtils; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.MatrixVariable; @@ -587,6 +588,24 @@ public class SpringMvcContractTests { assertThat(data.formParams()).contains("file", "id"); } + @Test + public void testSingleCookieAnnotation() throws NoSuchMethodException { + Method method = TestTemplate_Cookies.class.getDeclaredMethod("singleCookie", String.class, String.class); + + MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); + assertThat(data.template().headers().get("cookie").iterator().next()).isEqualTo("cookie1={cookie1}"); + } + + @Test + public void testMultipleCookiesAnnotation() throws NoSuchMethodException { + Method method = TestTemplate_Cookies.class.getDeclaredMethod("multipleCookies", String.class, String.class, + String.class); + + MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method); + assertThat(data.template().headers().get("cookie").iterator().next()) + .isEqualTo("cookie1={cookie1}; cookie2={cookie2}"); + } + private ConversionService getConversionService() { FormattingConversionServiceFactoryBean conversionServiceFactoryBean = new FormattingConversionServiceFactoryBean(); conversionServiceFactoryBean.afterPropertiesSet(); @@ -637,6 +656,17 @@ public class SpringMvcContractTests { } + public interface TestTemplate_Cookies { + + @GetMapping("/test/{id}") + ResponseEntity singleCookie(@PathVariable("id") String id, @CookieValue("cookie1") String cookie1); + + @GetMapping("/test/{id}") + ResponseEntity multipleCookies(@PathVariable("id") String id, + @CookieValue("cookie1") String cookie1, @CookieValue("cookie2") String cookie2); + + } + public interface TestTemplate_HeadersWithoutValues { @GetMapping(value = "/test/{id}", headers = { "X-Foo", "!X-Bar", "X-Baz!=fooBar" })