diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/support/SpringMvcContract.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/support/SpringMvcContract.java index 97723a5f0..7dab129c0 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/support/SpringMvcContract.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/support/SpringMvcContract.java @@ -106,6 +106,25 @@ public class SpringMvcContract extends Contract.BaseContract this.resourceLoader = resourceLoader; } + @Override + protected void processAnnotationOnClass(MethodMetadata data, Class clz) { + if (clz.getInterfaces().length == 0) { + RequestMapping classAnnotation = findMergedAnnotation(clz, + RequestMapping.class); + if (classAnnotation != null) { + // Prepend path from class annotation if specified + if (classAnnotation.value().length > 0) { + String pathValue = emptyToNull(classAnnotation.value()[0]); + pathValue = resolve(pathValue); + if (!pathValue.startsWith("/")) { + pathValue = "/" + pathValue; + } + data.template().insert(0, pathValue); + } + } + } + } + @Override public MethodMetadata parseAndValidateMetadata(Class targetType, Method method) { this.processedMethods.put(Feign.configKey(targetType, method), method); @@ -114,19 +133,6 @@ public class SpringMvcContract extends Contract.BaseContract RequestMapping classAnnotation = findMergedAnnotation(targetType, RequestMapping.class); if (classAnnotation != null) { - // Prepend path from class annotation if specified - if (classAnnotation.value().length > 0) { - String pathValue = emptyToNull(classAnnotation.value()[0]); - checkState(pathValue != null, - "RequestMapping.value() was empty on type %s", - method.getDeclaringClass().getName()); - pathValue = resolve(pathValue); - if (!pathValue.startsWith("/")) { - pathValue = "/" + pathValue; - } - md.template().insert(0, pathValue); - } - // produces - use from class annotation only if method has not specified this if (!md.template().headers().containsKey(ACCEPT)) { parseProduces(md, method, classAnnotation); diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/support/SpringMvcContractTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/support/SpringMvcContractTests.java index bf91e5359..1cf2a3fde 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/support/SpringMvcContractTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/support/SpringMvcContractTests.java @@ -34,14 +34,14 @@ import org.springframework.web.bind.annotation.RequestParam; import com.fasterxml.jackson.annotation.JsonAutoDetect; -import static org.junit.Assert.assertEquals; -import static org.junit.Assume.assumeTrue; - import feign.MethodMetadata; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; import lombok.ToString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assume.assumeTrue; + /** * @author chadjaros */ @@ -94,6 +94,53 @@ public class SpringMvcContractTests { assertEquals("id", data.indexToName().get(0).iterator().next()); } + @Test + public void testProcessAnnotations_Class_AnnotationsGetSpecificTest() + throws Exception { + Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod( + "getSpecificTest", String.class, String.class); + MethodMetadata data = this.contract.parseAndValidateMetadata( + method.getDeclaringClass(), method); + + assertEquals("/prepend/{classId}/test/{testId}", data.template().url()); + assertEquals("GET", data.template().method()); + + assertEquals("classId", data.indexToName().get(0).iterator().next()); + assertEquals("testId", data.indexToName().get(1).iterator().next()); + } + + @Test + public void testProcessAnnotations_Class_AnnotationsGetAllTests() throws Exception { + Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod( + "getAllTests", String.class); + MethodMetadata data = this.contract.parseAndValidateMetadata( + method.getDeclaringClass(), method); + + assertEquals("/prepend/{classId}", data.template().url()); + assertEquals("GET", data.template().method()); + + assertEquals("classId", data.indexToName().get(0).iterator().next()); + } + + @Test + public void testProcessAnnotations_ExtendedInterface() throws Exception { + Method extendedMethod = TestTemplate_Extended.class.getMethod("getAllTests", + String.class); + MethodMetadata extendedData = this.contract.parseAndValidateMetadata( + extendedMethod.getDeclaringClass(), extendedMethod); + + Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod( + "getAllTests", String.class); + MethodMetadata data = this.contract.parseAndValidateMetadata( + method.getDeclaringClass(), method); + + assertEquals(extendedData.template().url(), data.template().url()); + assertEquals(extendedData.template().method(), data.template().method()); + + assertEquals(data.indexToName().get(0).iterator().next(), + data.indexToName().get(0).iterator().next()); + } + @Test public void testProcessAnnotations_SimplePost() throws Exception { Method method = TestTemplate_Simple.class.getDeclaredMethod("postTest", @@ -273,6 +320,20 @@ public class SpringMvcContractTests { TestObject postTest(@RequestBody TestObject object); } + @RequestMapping("/prepend/{classId}") + public interface TestTemplate_Class_Annotations { + @RequestMapping(value = "/test/{testId}", method = RequestMethod.GET) + TestObject getSpecificTest(@PathVariable("classId") String classId, + @PathVariable("testId") String testId); + + @RequestMapping(method = RequestMethod.GET) + TestObject getAllTests(@PathVariable("classId") String classId); + } + + public interface TestTemplate_Extended extends TestTemplate_Class_Annotations { + + } + public interface TestTemplate_Headers { @RequestMapping(value = "/test/{id}", method = RequestMethod.GET, headers = "X-Foo=bar") ResponseEntity getTest(@PathVariable("id") String id);