diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java index d4db3704d..1dec131c4 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java @@ -28,7 +28,6 @@ import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.servlet.config.annotation.CorsRegistration; /** @@ -565,22 +564,6 @@ public class RepositoryRestConfiguration { return corsRegistry; } - /** - * Configures Cross-origin resource sharing given a {@code path}. - * - * @param path path or path pattern, must not be {@literal null} or empty. - * @return the {@link CorsRegistration} to build a CORS configuration. - * @since 2.6 - * @see CorsConfiguration - */ - public CorsRegistration addCorsMapping(String path) { - - Assert.notNull(path, "Path must not be null!"); - Assert.hasText(path, "Path must not be empty!"); - - return corsRegistry.addMapping(path); - } - /** * Returns the {@link EntityLookupRegistrar} to create custom {@link EntityLookup} instances registered in the * configuration. diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/RepositoryRestConfigurationUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/RepositoryRestConfigurationUnitTests.java index 009424997..6a926fdbc 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/RepositoryRestConfigurationUnitTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/RepositoryRestConfigurationUnitTests.java @@ -26,6 +26,7 @@ import org.junit.Test; import org.springframework.data.rest.core.config.EnumTranslationConfiguration; import org.springframework.data.rest.core.config.MetadataConfiguration; import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration; +import org.springframework.data.rest.core.config.RepositoryCorsRegistry; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.core.domain.Profile; import org.springframework.data.rest.core.domain.ProfileRepository; @@ -149,9 +150,10 @@ public class RepositoryRestConfigurationUnitTests { @Test public void configuresCorsProcessing() { - configuration.addCorsMapping("/hello").maxAge(1234); + RepositoryCorsRegistry registry = configuration.getCorsRegistry(); + registry.addMapping("/hello").maxAge(1234); - Map corsConfigurations = configuration.getCorsRegistry().getCorsConfigurations(); + Map corsConfigurations = registry.getCorsConfigurations(); assertThat(corsConfigurations, hasKey("/hello")); CorsConfiguration corsConfiguration = corsConfigurations.get("/hello"); diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/AuthorRepository.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/AuthorRepository.java index d2c57c4c2..87f7744a5 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/AuthorRepository.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/main/java/org/springframework/data/rest/webmvc/jpa/AuthorRepository.java @@ -15,14 +15,17 @@ */ package org.springframework.data.rest.webmvc.jpa; +import static org.springframework.web.bind.annotation.RequestMethod.*; + import org.springframework.data.repository.CrudRepository; import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.RequestMethod; /** * @author Oliver Gierke * @author Mark Paluch */ -@CrossOrigin(origins = "http://not.so.far.away", allowCredentials = "true", - methods = { RequestMethod.GET, RequestMethod.PATCH }, maxAge = 1234) +@CrossOrigin(origins = "http://not.so.far.away", // + allowCredentials = "true", // + methods = { GET, PATCH }, // + maxAge = 1234) public interface AuthorRepository extends CrudRepository {} diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/CorsIntegrationTests.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/CorsIntegrationTests.java index a8f5788ec..96c886524 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/CorsIntegrationTests.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/jpa/CorsIntegrationTests.java @@ -15,11 +15,9 @@ */ package org.springframework.data.rest.webmvc.jpa; -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import org.junit.Test; import org.springframework.context.annotation.Bean; @@ -58,7 +56,7 @@ public class CorsIntegrationTests extends AbstractWebIntegrationTests { @Override public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) { - config.addCorsMapping("/books/**") // + config.getCorsRegistry().addMapping("/books/**") // .allowedMethods("GET", "PUT", "POST") // .allowedOrigins("http://far.far.away"); } @@ -79,7 +77,8 @@ public class CorsIntegrationTests extends AbstractWebIntegrationTests { .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST")) // .andExpect(status().isOk()) // .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "http://far.far.away")) // - .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE")); + .andExpect( + header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE")); } /** diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java index f9fa7f3c4..8c45e07fc 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java @@ -15,6 +15,9 @@ */ package org.springframework.data.rest.webmvc; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; @@ -66,7 +69,7 @@ public class RepositoryRestHandlerMapping extends BasePathAwareHandlerMapping { private final RepositoryRestConfiguration configuration; private final Repositories repositories; - private StringValueResolver embeddedValueResolver; + private RepositoryCorsConfigurationAccessor corsConfigurationAccessor; private JpaHelper jpaHelper; /** @@ -99,6 +102,8 @@ public class RepositoryRestHandlerMapping extends BasePathAwareHandlerMapping { this.mappings = mappings; this.configuration = config; this.repositories = repositories; + this.corsConfigurationAccessor = new RepositoryCorsConfigurationAccessor(mappings, repositories, + NoOpStringValueResolver.INSTANCE); } /** @@ -108,14 +113,17 @@ public class RepositoryRestHandlerMapping extends BasePathAwareHandlerMapping { this.jpaHelper = jpaHelper; } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#setEmbeddedValueResolver(org.springframework.util.StringValueResolver) */ @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { - embeddedValueResolver = resolver; super.setEmbeddedValueResolver(resolver); + + this.corsConfigurationAccessor = new RepositoryCorsConfigurationAccessor(mappings, repositories, + resolver == null ? NoOpStringValueResolver.INSTANCE : resolver); } /* @@ -207,15 +215,10 @@ public class RepositoryRestHandlerMapping extends BasePathAwareHandlerMapping { return corsConfiguration; } - // Repository root resource - CorsConfiguration repositoryConfiguration = new CorsConfigurationAccessor(mappings, repositories, - embeddedValueResolver).findCorsConfiguration(lookupPath); + CorsConfiguration repositoryCorsConfiguration = corsConfigurationAccessor.findCorsConfiguration(lookupPath); - if (repositoryConfiguration != null) { - return corsConfiguration != null ? corsConfiguration.combine(repositoryConfiguration) : repositoryConfiguration; - } - - return corsConfiguration; + return corsConfiguration == null ? repositoryCorsConfiguration + : corsConfiguration.combine(repositoryCorsConfiguration); } /** @@ -231,37 +234,40 @@ public class RepositoryRestHandlerMapping extends BasePathAwareHandlerMapping { } /** - * Accessor to obtain {@link CorsConfiguration} for exposed repositories. - *

- * Exported Repository classes can be annotated with {@link CrossOrigin} to configure CORS for a specific repository. + * No-op {@link StringValueResolver} that returns the given {@link String} value as is. * - * @author Mark Paluch + * @author Oliver Gierke * @since 2.6 */ - static class CorsConfigurationAccessor { + enum NoOpStringValueResolver implements StringValueResolver { - private final ResourceMappings mappings; - private final Repositories repositories; - private final StringValueResolver embeddedValueResolver; + INSTANCE; - /** - * Creates a new {@link CorsConfigurationAccessor} given {@link ResourceMappings}, {@link Repositories} and - * {@link StringValueResolver}. - * - * @param mappings must not be {@literal null}. - * @param repositories must not be {@literal null}. - * @param embeddedValueResolver may be {@literal null} if not present. + /* + * (non-Javadoc) + * @see org.springframework.util.StringValueResolver#resolveStringValue(java.lang.String) */ - CorsConfigurationAccessor(ResourceMappings mappings, Repositories repositories, - StringValueResolver embeddedValueResolver) { - - Assert.notNull(mappings, "ResourceMappings must not be null!"); - Assert.notNull(repositories, "Repositories must not be null!"); - - this.mappings = mappings; - this.repositories = repositories; - this.embeddedValueResolver = embeddedValueResolver; + @Override + public String resolveStringValue(String value) { + return value; } + } + + /** + * Accessor to obtain {@link CorsConfiguration} for exposed repositories. + *

+ * Exported repository classes can be annotated with {@link CrossOrigin} to configure CORS for a specific repository. + * + * @author Mark Paluch + * @author Oliver Gierke + * @since 2.6 + */ + @RequiredArgsConstructor + static class RepositoryCorsConfigurationAccessor { + + private final @NonNull ResourceMappings mappings; + private final @NonNull Repositories repositories; + private final @NonNull StringValueResolver embeddedValueResolver; CorsConfiguration findCorsConfiguration(String lookupPath) { @@ -305,7 +311,7 @@ public class RepositoryRestHandlerMapping extends BasePathAwareHandlerMapping { if (CollectionUtils.isEmpty(config.getAllowedOrigins())) { config.setAllowedOrigins(Arrays.asList(CrossOrigin.DEFAULT_ORIGINS)); } - + if (CollectionUtils.isEmpty(config.getAllowedMethods())) { for (HttpMethod httpMethod : HttpMethod.values()) { config.addAllowedMethod(httpMethod); @@ -362,7 +368,7 @@ public class RepositoryRestHandlerMapping extends BasePathAwareHandlerMapping { } private String resolveCorsAnnotationValue(String value) { - return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value); + return this.embeddedValueResolver.resolveStringValue(value); } } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/CorsConfigurationAccessorUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryCorsConfigurationAccessorUnitTests.java similarity index 81% rename from spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/CorsConfigurationAccessorUnitTests.java rename to spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryCorsConfigurationAccessorUnitTests.java index c41f2a34a..f5175ed3b 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/CorsConfigurationAccessorUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryCorsConfigurationAccessorUnitTests.java @@ -15,7 +15,7 @@ */ package org.springframework.data.rest.webmvc; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; import org.junit.Before; @@ -25,28 +25,31 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.mapping.ResourceMappings; -import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping.CorsConfigurationAccessor; +import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping.NoOpStringValueResolver; +import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping.RepositoryCorsConfigurationAccessor; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.cors.CorsConfiguration; /** - * Unit tests for {@link CorsConfigurationAccessor}. + * Unit tests for {@link RepositoryCorsConfigurationAccessor}. * * @author Mark Paluch + * @author Oliver Gierke * @soundtrack Aso Mamiko - Drive Me Crazy (Club Mix) + * @since 2.6 */ @RunWith(MockitoJUnitRunner.class) -public class CorsConfigurationAccessorUnitTests { +public class RepositoryCorsConfigurationAccessorUnitTests { - CorsConfigurationAccessor accessor; + RepositoryCorsConfigurationAccessor accessor; @Mock ResourceMappings mappings; @Mock Repositories repositories; @Before public void before() throws Exception { - accessor = new CorsConfigurationAccessor(mappings, repositories, null); + accessor = new RepositoryCorsConfigurationAccessor(mappings, repositories, NoOpStringValueResolver.INSTANCE); } /** @@ -90,7 +93,10 @@ public class CorsConfigurationAccessorUnitTests { @CrossOrigin interface AnnotatedRepository {} - @CrossOrigin(origins = "http://far.far.away", allowedHeaders = "Content-type", maxAge = 1234, - exposedHeaders = "Accept", methods = RequestMethod.PATCH, allowCredentials = "true") + @CrossOrigin(origins = "http://far.far.away", // + allowedHeaders = "Content-type", // + maxAge = 1234, exposedHeaders = "Accept", // + methods = RequestMethod.PATCH, // + allowCredentials = "true") interface FullyConfiguredCorsRepository {} } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java index a1c99c45a..416234e7b 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java @@ -27,6 +27,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.data.domain.Sort; +import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.config.EnumTranslationConfiguration; import org.springframework.data.rest.core.config.MetadataConfiguration; import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration; @@ -57,6 +58,7 @@ public class RepositoryRestHandlerMappingUnitTests { } @Mock ResourceMappings mappings; + @Mock Repositories repositories; RepositoryRestConfiguration configuration; RepositoryRestHandlerMapping handlerMapping; @@ -69,7 +71,7 @@ public class RepositoryRestHandlerMappingUnitTests { configuration = new RepositoryRestConfiguration(new ProjectionDefinitionConfiguration(), new MetadataConfiguration(), mock(EnumTranslationConfiguration.class)); - handlerMapping = new RepositoryRestHandlerMapping(mappings, configuration); + handlerMapping = new RepositoryRestHandlerMapping(mappings, configuration, repositories); handlerMapping.setApplicationContext(CONTEXT); mockRequest = new MockHttpServletRequest(); diff --git a/src/main/asciidoc/configuring-cors.adoc b/src/main/asciidoc/configuring-cors.adoc index 9b18096b4..0b330c3a0 100644 --- a/src/main/asciidoc/configuring-cors.adoc +++ b/src/main/asciidoc/configuring-cors.adoc @@ -21,7 +21,8 @@ In the above example CORS support is enabled for the whole `PersonRepository`. ` [source, java] ---- @CrossOrigin(origins = "http://domain2.com", - methods = { RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE }, maxAge = 3600) + methods = { RequestMethod.GET, RequestMethod.POST, RequestMethod.DELETE }, + maxAge = 3600) interface PersonRepository extends CrudRepository {} ---- @@ -34,14 +35,13 @@ Spring Data REST fully supports http://docs.spring.io/spring/docs/current/spring [source, java] ---- @RepositoryRestController -@RequestMapping("/person") public class PersonController { - @CrossOrigin(maxAge = 3600) - @RequestMapping(method = RequestMethod.GET, "/xml/{id}", produces = MediaType.APPLICATION_XML_VALUE) - public Person retrieve(@PathVariable Long id) { - // ... - } + @CrossOrigin(maxAge = 3600) + @RequestMapping(path = "/people/xml/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_XML_VALUE) + public Person retrieve(@PathVariable Long id) { + // … + } } ---- @@ -61,12 +61,12 @@ public class SpringDataRestCustomization extends RepositoryRestConfigurerAdapter @Override public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) { - config.addCorsMapping("/person/**") - .allowedOrigins("http://domain2.com") - .allowedMethods("PUT", "DELETE") - .allowedHeaders("header1", "header2", "header3") - .exposedHeaders("header1", "header2") - .allowCredentials(false).maxAge(3600); + config.getCorsRegistry().addCorsMapping("/person/**") + .allowedOrigins("http://domain2.com") + .allowedMethods("PUT", "DELETE") + .allowedHeaders("header1", "header2", "header3") + .exposedHeaders("header1", "header2") + .allowCredentials(false).maxAge(3600); } } ----