diff --git a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc index 123cc6d0..65f4701c 100644 --- a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc +++ b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc @@ -482,7 +482,7 @@ The following feign client uses the `Params` class by using the `@SpringQueryMap [source,java,indent=0] ---- @FeignClient("demo") -public class DemoTemplate { +public interface DemoTemplate { @GetMapping(path = "/demo") String demoEndpoint(@SpringQueryMap Params params); @@ -490,3 +490,23 @@ public class DemoTemplate { ---- If you need more control over the generated query parameter map, you can implement a custom `QueryMapEncoder` bean. + +=== HATEOAS support + +Spring provides some APIs to create REST representations that follow the https://en.wikipedia.org/wiki/HATEOAS[HATEOAS] principle, https://spring.io/projects/spring-hateoas[Spring Hateoas] and https://spring.io/projects/spring-data-rest[Spring Data REST]. + +If your project use the `org.springframework.boot:spring-boot-starter-hateoas` starter +or the `org.springframework.boot:spring-boot-starter-data-rest` starter, Feign HATEOAS support is enabled by default. + +When HATEOAS support is enabled, Feign clients are allowed to serialize +and deserialize HATEOAS representation models: https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/EntityModel.html[EntityModel], https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/CollectionModel.html[CollectionModel] and https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/PagedModel.html[PagedModel]. + +[source,java,indent=0] +---- +@FeignClient("demo") +public interface DemoTemplate { + + @GetMapping(path = "/stores") + CollectionModel getStores(); +} +---- diff --git a/spring-cloud-openfeign-core/pom.xml b/spring-cloud-openfeign-core/pom.xml index 92d1bf1b..6014b903 100644 --- a/spring-cloud-openfeign-core/pom.xml +++ b/spring-cloud-openfeign-core/pom.xml @@ -50,6 +50,11 @@ spring-boot-starter-reactor-netty true + + org.springframework.boot + spring-boot-starter-hateoas + true + io.netty netty-codec-http @@ -208,6 +213,11 @@ spring-cloud-starter-netflix-hystrix test + + org.springframework.boot + spring-boot-starter-data-rest + test + com.googlecode.protobuf-java-format protobuf-java-format diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/hateoas/FeignHalAutoConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/hateoas/FeignHalAutoConfiguration.java new file mode 100644 index 00000000..9428169a --- /dev/null +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/hateoas/FeignHalAutoConfiguration.java @@ -0,0 +1,97 @@ +/* + * Copyright 2016-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.hateoas; + +import java.util.Arrays; +import java.util.Collections; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; +import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.hateoas.RepresentationModel; +import org.springframework.hateoas.mediatype.hal.CurieProvider; +import org.springframework.hateoas.mediatype.hal.DefaultCurieProvider; +import org.springframework.hateoas.mediatype.hal.HalConfiguration; +import org.springframework.hateoas.mediatype.hal.Jackson2HalModule; +import org.springframework.hateoas.server.LinkRelationProvider; +import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; + +import static org.springframework.hateoas.MediaTypes.HAL_JSON; +import static org.springframework.hateoas.MediaTypes.HAL_JSON_UTF8; + +/** + * @author Hector Espert + */ +@Configuration +@ConditionalOnWebApplication +@ConditionalOnClass(RepresentationModel.class) +@AutoConfigureAfter({ JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + RepositoryRestMvcAutoConfiguration.class }) +@AutoConfigureBefore(HypermediaAutoConfiguration.class) +public class FeignHalAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public TypeConstrainedMappingJackson2HttpMessageConverter halJacksonHttpMessageConverter( + ObjectProvider objectMapper, + ObjectProvider halConfiguration, + ObjectProvider relProvider, + ObjectProvider curieProvider, + ObjectProvider linkRelationMessageSource) { + + ObjectMapper mapper = objectMapper.getIfAvailable(ObjectMapper::new).copy(); + mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + + HalConfiguration configuration = halConfiguration + .getIfAvailable(HalConfiguration::new); + + CurieProvider curieProviderInstance = curieProvider + .getIfAvailable(() -> new DefaultCurieProvider(Collections.emptyMap())); + + Jackson2HalModule.HalHandlerInstantiator halHandlerInstantiator = new Jackson2HalModule.HalHandlerInstantiator( + relProvider.getObject(), curieProviderInstance, + linkRelationMessageSource.getObject(), configuration); + + mapper.setHandlerInstantiator(halHandlerInstantiator); + + if (!Jackson2HalModule.isAlreadyRegisteredIn(mapper)) { + Jackson2HalModule halModule = new Jackson2HalModule(); + mapper.registerModule(halModule); + } + + TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter( + RepresentationModel.class); + converter.setSupportedMediaTypes(Arrays.asList(HAL_JSON, HAL_JSON_UTF8)); + converter.setObjectMapper(mapper); + return converter; + } + +} diff --git a/spring-cloud-openfeign-core/src/main/resources/META-INF/spring.factories b/spring-cloud-openfeign-core/src/main/resources/META-INF/spring.factories index 51c962ab..27e7c30c 100644 --- a/spring-cloud-openfeign-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-openfeign-core/src/main/resources/META-INF/spring.factories @@ -1,5 +1,6 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.cloud.openfeign.ribbon.FeignRibbonClientAutoConfiguration,\ +org.springframework.cloud.openfeign.hateoas.FeignHalAutoConfiguration,\ org.springframework.cloud.openfeign.FeignAutoConfiguration,\ org.springframework.cloud.openfeign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\ org.springframework.cloud.openfeign.encoding.FeignContentGzipEncodingAutoConfiguration diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java index d19255c0..7d20923f 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/encoding/FeignPageableEncodingTests.java @@ -27,6 +27,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.openfeign.EnableFeignClients; @@ -86,7 +87,8 @@ public class FeignPageableEncodingTests { @EnableFeignClients(clients = InvoiceClient.class) @RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class) @SpringBootApplication( - scanBasePackages = "org.springframework.cloud.openfeign.encoding.app") + scanBasePackages = "org.springframework.cloud.openfeign.encoding.app", + exclude = { RepositoryRestMvcAutoConfiguration.class }) @EnableSpringDataWebSupport @Import({ NoSecurityConfiguration.class, FeignClientsConfiguration.class }) public static class Application { diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/FeignHalAutoConfigurationContextTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/FeignHalAutoConfigurationContextTests.java new file mode 100644 index 00000000..20e78d90 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/FeignHalAutoConfigurationContextTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2016-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.hateoas; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; +import org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration; +import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; +import org.springframework.boot.test.context.FilteredClassLoader; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; +import org.springframework.hateoas.RepresentationModel; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Hector Espert + */ +public class FeignHalAutoConfigurationContextTests { + + private WebApplicationContextRunner contextRunner; + + @Before + public void setUp() { + contextRunner = new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, + HypermediaAutoConfiguration.class, + RepositoryRestMvcAutoConfiguration.class, + FeignHalAutoConfiguration.class)) + .withPropertyValues("debug=true"); + } + + @Test + public void testHalJacksonHttpMessageConverterIsNotLoaded() { + FilteredClassLoader filteredClassLoader = new FilteredClassLoader( + RepositoryRestMvcConfiguration.class, RepresentationModel.class); + contextRunner.withClassLoader(filteredClassLoader) + .run(context -> assertThat(context) + .doesNotHaveBean("halJacksonHttpMessageConverter")); + } + + @Test + public void testHalJacksonHttpMessageConverterIsLoaded() { + FilteredClassLoader filteredClassLoader = new FilteredClassLoader( + RepositoryRestMvcConfiguration.class); + contextRunner.withClassLoader(filteredClassLoader).run( + context -> assertThat(context).hasBean("halJacksonHttpMessageConverter")); + } + + @Test + public void testHalJacksonHttpMessageConverterIsNotLoadedUseRestDataMessageConverterInstead() { + contextRunner.run( + context -> assertThat(context).hasBean("halJacksonHttpMessageConverter")); + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/FeignHalAutoConfigurationTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/FeignHalAutoConfigurationTests.java new file mode 100644 index 00000000..80670f90 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/FeignHalAutoConfigurationTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016-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.hateoas; + +import java.util.Arrays; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.hateoas.mediatype.hal.CurieProvider; +import org.springframework.hateoas.mediatype.hal.HalConfiguration; +import org.springframework.hateoas.mediatype.hal.Jackson2HalModule; +import org.springframework.hateoas.server.LinkRelationProvider; +import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.hateoas.MediaTypes.HAL_JSON; +import static org.springframework.hateoas.MediaTypes.HAL_JSON_UTF8; + +/** + * @author Hector Espert + */ +@RunWith(MockitoJUnitRunner.class) +public class FeignHalAutoConfigurationTests { + + @Mock + private ObjectProvider objectMapper; + + @Mock + private ObjectProvider halConfiguration; + + @Mock + private ObjectProvider relProvider; + + @Mock + private ObjectProvider curieProvider; + + @Mock + private ObjectProvider linkRelationMessageSource; + + @InjectMocks + private FeignHalAutoConfiguration feignHalAutoConfiguration; + + @Test + public void halJacksonHttpMessageConverter() { + ObjectMapper mapper = new ObjectMapper(); + when(objectMapper.getIfAvailable(any())).thenReturn(mapper); + + when(halConfiguration.getIfAvailable(any())) + .thenReturn(mock(HalConfiguration.class)); + when(relProvider.getObject()).thenReturn(mock(LinkRelationProvider.class)); + when(curieProvider.getIfAvailable(any())).thenReturn(mock(CurieProvider.class)); + when(linkRelationMessageSource.getObject()) + .thenReturn(mock(MessageSourceAccessor.class)); + + TypeConstrainedMappingJackson2HttpMessageConverter converter = feignHalAutoConfiguration + .halJacksonHttpMessageConverter(objectMapper, halConfiguration, + relProvider, curieProvider, linkRelationMessageSource); + + assertThat(converter).isNotNull(); + assertThat(converter.getObjectMapper()).isNotNull(); + assertThat(converter.getSupportedMediaTypes()) + .isEqualTo(Arrays.asList(HAL_JSON, HAL_JSON_UTF8)); + + assertThat(Jackson2HalModule.isAlreadyRegisteredIn(converter.getObjectMapper())) + .isTrue(); + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/FeignHalTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/FeignHalTests.java new file mode 100644 index 00000000..b826f13a --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/FeignHalTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016-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.hateoas; + +import java.util.Collection; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.openfeign.hateoas.app.FeignHalApplication; +import org.springframework.cloud.openfeign.hateoas.app.FeignHalClient; +import org.springframework.cloud.openfeign.hateoas.app.MarsRover; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.PagedModel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * Test HATEOAS support. + * + * @author Hector Espert + */ +@SpringBootTest(classes = FeignHalApplication.class, webEnvironment = RANDOM_PORT, + value = "debug=true") +@RunWith(SpringRunner.class) +@DirtiesContext +public class FeignHalTests { + + @Autowired + private FeignHalClient feignHalClient; + + @Test + public void testEntityModel() { + EntityModel entity = feignHalClient.entity(); + assertThat(entity).isNotNull(); + + assertThat(entity.hasLinks()).isTrue(); + assertThat(entity.hasLink("self")).isTrue(); + + assertThat(entity.getLink("self")).map(Link::getHref).contains("/entity"); + + MarsRover marsRover = entity.getContent(); + assertThat(marsRover).isNotNull(); + assertThat(marsRover.getName()).isEqualTo("Sojourner"); + } + + @Test + public void testCollectionModel() { + CollectionModel collectionModel = feignHalClient.collection(); + assertThat(collectionModel).isNotNull(); + assertThat(collectionModel).isNotEmpty(); + + assertThat(collectionModel.hasLinks()).isTrue(); + assertThat(collectionModel.hasLink("self")).isTrue(); + + assertThat(collectionModel.getLink("self")).map(Link::getHref) + .contains("/collection"); + + Collection collection = collectionModel.getContent(); + assertThat(collection).isNotEmpty(); + + MarsRover marsRover = collection.stream().findAny().orElse(null); + assertThat(marsRover).isNotNull(); + assertThat(marsRover.getName()).isEqualTo("Opportunity"); + } + + @Test + public void testPagedModel() { + PagedModel paged = feignHalClient.paged(); + assertThat(paged).isNotNull(); + assertThat(paged).isNotEmpty(); + + assertThat(paged.hasLinks()).isTrue(); + assertThat(paged.hasLink("self")).isTrue(); + + assertThat(paged.getLink("self")).map(Link::getHref).contains("/paged"); + + Collection collection = paged.getContent(); + assertThat(collection).isNotEmpty(); + + MarsRover marsRover = collection.stream().findAny().orElse(null); + assertThat(marsRover).isNotNull(); + assertThat(marsRover.getName()).isEqualTo("Curiosity"); + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalApplication.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalApplication.java new file mode 100644 index 00000000..d95fb093 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalApplication.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016-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.hateoas.app; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration; +import org.springframework.cloud.netflix.ribbon.RibbonClient; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.test.NoSecurityConfiguration; +import org.springframework.context.annotation.Import; + +/** + * Test HATEOAS application. + * + * @author Hector Espert + */ +@EnableFeignClients(clients = FeignHalClient.class) +@RibbonClient(name = "local", configuration = FeignHalRibbonConfiguration.class) +@SpringBootApplication( + scanBasePackages = "org.springframework.cloud.openfeign.hateoas.app", + exclude = RepositoryRestMvcAutoConfiguration.class) +@Import(NoSecurityConfiguration.class) +public class FeignHalApplication { + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalClient.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalClient.java new file mode 100644 index 00000000..cfa41b7f --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalClient.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016-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.hateoas.app; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.PagedModel; +import org.springframework.web.bind.annotation.GetMapping; + +/** + * @author Hector Espert + */ +@FeignClient("local") +public interface FeignHalClient { + + @GetMapping("entity") + EntityModel entity(); + + @GetMapping("collection") + CollectionModel collection(); + + @GetMapping("paged") + PagedModel paged(); + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalController.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalController.java new file mode 100644 index 00000000..a8ec3d75 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalController.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016-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.hateoas.app; + +import java.util.Collections; + +import org.springframework.hateoas.CollectionModel; +import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.PagedModel; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Hector Espert + */ +@RestController +public class FeignHalController { + + @GetMapping("/entity") + public EntityModel getEntity() { + MarsRover marsRover = new MarsRover(); + marsRover.setName("Sojourner"); + Link link = new Link("/entity", "self"); + return new EntityModel<>(marsRover, link); + } + + @GetMapping("/collection") + public CollectionModel getCollection() { + MarsRover marsRover = new MarsRover(); + marsRover.setName("Opportunity"); + Link link = new Link("/collection", "self"); + return new CollectionModel<>(Collections.singleton(marsRover), link); + } + + @GetMapping("/paged") + public CollectionModel getPaged() { + MarsRover marsRover = new MarsRover(); + marsRover.setName("Curiosity"); + Link link = new Link("/paged", "self"); + PagedModel.PageMetadata metadata = new PagedModel.PageMetadata(1, 1, 1); + return new PagedModel<>(Collections.singleton(marsRover), metadata, link); + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalRibbonConfiguration.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalRibbonConfiguration.java new file mode 100644 index 00000000..ef4ff7c2 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/FeignHalRibbonConfiguration.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016-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.hateoas.app; + +import java.util.Collections; + +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; + +/** + * @author Hector Espert + */ +public class FeignHalRibbonConfiguration { + + @Value("${local.server.port}") + private int serverPort = 0; + + @Bean + public ILoadBalancer ribbonLoadBalancer() { + Server server = new Server("localhost", serverPort); + BaseLoadBalancer balancer = new BaseLoadBalancer(); + balancer.setServersList(Collections.singletonList(server)); + return balancer; + } + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/MarsRover.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/MarsRover.java new file mode 100644 index 00000000..903cf164 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hateoas/app/MarsRover.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016-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.hateoas.app; + +/** + * @author Hector Espert + */ +public class MarsRover { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +}