Avoid late registration of PathPatternParser on DelegatingHandlerMapping.
Instead of registering the PathPatternParser on DelegatingHandlerMapping via WebMvcConfigurer.configurePathMatch(…) we now consume the bean exposed in context of the fix for spring-projects/spring-framework#26427. We also use the newly introduced RequestMappingInfo.mutate() to add our customizations of the produces clause for Spring Data REST's mappings. Fixes GH-1965.
This commit is contained in:
@@ -22,6 +22,7 @@ import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -122,9 +123,11 @@ public class BasePathAwareHandlerMapping extends RequestMappingHandlerMapping {
|
||||
}
|
||||
|
||||
ProducesRequestCondition producesCondition = customize(info.getProducesCondition());
|
||||
Set<MediaType> mediaTypes = producesCondition.getProducibleMediaTypes();
|
||||
|
||||
return new RequestMappingInfo(info.getPatternsCondition(), info.getMethodsCondition(), info.getParamsCondition(),
|
||||
info.getHeadersCondition(), info.getConsumesCondition(), producesCondition, info.getCustomCondition());
|
||||
return info.mutate()
|
||||
.produces(mediaTypes.stream().map(MediaType::toString).toArray(String[]::new))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
|
||||
import org.springframework.web.servlet.mvc.condition.ProducesRequestCondition;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
@@ -266,13 +267,29 @@ public class RepositoryRestHandlerMapping extends BasePathAwareHandlerMapping {
|
||||
return;
|
||||
}
|
||||
|
||||
String pattern = mappingInfo.getPatternsCondition() //
|
||||
.getMatchingCondition(request)//
|
||||
.getPatterns() //
|
||||
.iterator().next();
|
||||
String pattern = getPattern(mappingInfo, request);
|
||||
|
||||
PathPatternParser parser = getPatternParser();
|
||||
parser = parser != null ? parser : PARSER;
|
||||
|
||||
request.setAttribute(EFFECTIVE_LOOKUP_PATH_ATTRIBUTE,
|
||||
PARSER.parse(pattern.replace("/{repository}", repositoryBasePath)));
|
||||
parser.parse(pattern.replace("/{repository}", repositoryBasePath)));
|
||||
}
|
||||
|
||||
private static String getPattern(RequestMappingInfo info, HttpServletRequest request) {
|
||||
|
||||
PathPatternsRequestCondition pathPatternsCondition = info.getPathPatternsCondition();
|
||||
|
||||
if (pathPatternsCondition != null) {
|
||||
return pathPatternsCondition
|
||||
.getMatchingCondition(request)
|
||||
.getFirstPattern().getPatternString();
|
||||
}
|
||||
|
||||
return info.getPatternsCondition() //
|
||||
.getMatchingCondition(request)//
|
||||
.getPatterns()
|
||||
.iterator().next();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Objects;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
@@ -29,7 +30,6 @@ import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.UnsatisfiedServletRequestParameterException;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
|
||||
import org.springframework.web.servlet.handler.MatchableHandlerMapping;
|
||||
import org.springframework.web.servlet.handler.RequestMatchResult;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
@@ -45,25 +45,28 @@ class DelegatingHandlerMapping
|
||||
implements org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping, Ordered {
|
||||
|
||||
private final List<HandlerMapping> delegates;
|
||||
private final @Nullable PathPatternParser parser;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DelegatingHandlerMapping} for the given delegates.
|
||||
*
|
||||
* @param delegates must not be {@literal null}.
|
||||
*/
|
||||
public DelegatingHandlerMapping(List<HandlerMapping> delegates) {
|
||||
public DelegatingHandlerMapping(List<HandlerMapping> delegates, @Nullable PathPatternParser parser) {
|
||||
|
||||
Assert.notNull(delegates, "Delegates must not be null!");
|
||||
|
||||
this.delegates = delegates;
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
void setPatternParser(PathPatternParser parser) {
|
||||
|
||||
delegates.stream() //
|
||||
.filter(AbstractHandlerMapping.class::isInstance) //
|
||||
.map(AbstractHandlerMapping.class::cast) //
|
||||
.forEach(it -> it.setPatternParser(parser));
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.servlet.HandlerMapping#usesPathPatterns()
|
||||
*/
|
||||
@Override
|
||||
public boolean usesPathPatterns() {
|
||||
return parser != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("all")
|
||||
|
||||
@@ -119,11 +119,11 @@ import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.HandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
@@ -170,9 +170,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
ObjectProvider<RepresentationModelProcessorInvoker> invoker;
|
||||
ObjectProvider<MessageResolver> resolver;
|
||||
ObjectProvider<GeoModule> geoModule;
|
||||
|
||||
ConversionService defaultConversionService;
|
||||
|
||||
private final Lazy<ObjectMapper> mapper;
|
||||
private final ObjectProvider<PathPatternParser> parser;
|
||||
|
||||
private ClassLoader beanClassLoader;
|
||||
|
||||
@@ -183,7 +185,6 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
private Lazy<BaseUri> baseUri;
|
||||
private Lazy<RepositoryResourceMappings> resourceMappings;
|
||||
private Lazy<Repositories> repositories;
|
||||
private Lazy<DelegatingHandlerMapping> restHandlerMapping;
|
||||
private Lazy<ResourceMetadataHandlerMethodArgumentResolver> resourceMetadataHandlerMethodArgumentResolver;
|
||||
private Lazy<ExcerptProjector> excerptProjector;
|
||||
private Lazy<PersistentEntities> persistentEntities;
|
||||
@@ -206,7 +207,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
ObjectProvider<ObjectMapper> objectMapper, //
|
||||
ObjectProvider<RepresentationModelProcessorInvoker> invoker, //
|
||||
ObjectProvider<MessageResolver> resolver, //
|
||||
ObjectProvider<GeoModule> geoModule) {
|
||||
ObjectProvider<GeoModule> geoModule, //
|
||||
ObjectProvider<PathPatternParser> parser) {
|
||||
|
||||
super(context, conversionService);
|
||||
|
||||
@@ -217,6 +219,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
this.invoker = invoker;
|
||||
this.resolver = resolver;
|
||||
this.geoModule = geoModule;
|
||||
this.parser = parser;
|
||||
|
||||
this.mapper = Lazy.of(() -> {
|
||||
|
||||
@@ -241,7 +244,6 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
this.baseUri = Lazy.of(() -> context.getBean(BaseUri.class));
|
||||
this.resourceMappings = Lazy.of(() -> context.getBean(RepositoryResourceMappings.class));
|
||||
this.repositories = Lazy.of(() -> context.getBean(Repositories.class));
|
||||
this.restHandlerMapping = Lazy.of(() -> context.getBean("restHandlerMapping", DelegatingHandlerMapping.class));
|
||||
this.resourceMetadataHandlerMethodArgumentResolver = Lazy
|
||||
.of(() -> context.getBean(ResourceMetadataHandlerMethodArgumentResolver.class));
|
||||
this.excerptProjector = Lazy.of(() -> context.getBean(ExcerptProjector.class));
|
||||
@@ -347,6 +349,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
* Main configuration for the REST exporter.
|
||||
*/
|
||||
@Bean
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends RepositoryRestConfiguration & CorsConfigurationAware> T repositoryRestConfiguration() {
|
||||
|
||||
ProjectionDefinitionConfiguration configuration = new ProjectionDefinitionConfiguration();
|
||||
@@ -643,33 +646,27 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
RepositoryRestConfiguration repositoryRestConfiguration, CorsConfigurationAware corsRestConfiguration) {
|
||||
|
||||
Map<String, CorsConfiguration> corsConfigurations = corsRestConfiguration.getCorsConfigurations();
|
||||
PathPatternParser parser = this.parser.getIfAvailable();
|
||||
|
||||
RepositoryRestHandlerMapping repositoryMapping = new RepositoryRestHandlerMapping(resourceMappings,
|
||||
repositoryRestConfiguration, repositories);
|
||||
repositoryMapping.setJpaHelper(jpaHelper.orElse(null));
|
||||
repositoryMapping.setApplicationContext(applicationContext);
|
||||
repositoryMapping.setCorsConfigurations(corsConfigurations);
|
||||
repositoryMapping.setPatternParser(parser);
|
||||
repositoryMapping.afterPropertiesSet();
|
||||
|
||||
BasePathAwareHandlerMapping basePathMapping = new BasePathAwareHandlerMapping(repositoryRestConfiguration);
|
||||
basePathMapping.setApplicationContext(applicationContext);
|
||||
basePathMapping.setCorsConfigurations(corsConfigurations);
|
||||
basePathMapping.setPatternParser(parser);
|
||||
basePathMapping.afterPropertiesSet();
|
||||
|
||||
List<HandlerMapping> mappings = new ArrayList<>();
|
||||
mappings.add(basePathMapping);
|
||||
mappings.add(repositoryMapping);
|
||||
|
||||
return new DelegatingHandlerMapping(mappings);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#configurePathMatch(org.springframework.web.servlet.config.annotation.PathMatchConfigurer)
|
||||
*/
|
||||
@Override
|
||||
public void configurePathMatch(PathMatchConfigurer configurer) {
|
||||
restHandlerMapping.get().setPatternParser(configurer.getPatternParser());
|
||||
return new DelegatingHandlerMapping(mappings, parser);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -52,7 +52,7 @@ public class DelegatingHandlerMappingUnitTests {
|
||||
@Test // DATAREST-490, DATAREST-522, DATAREST-1387
|
||||
public void consultsAllHandlerMappingsAndThrowsExceptionEventually() throws Exception {
|
||||
|
||||
DelegatingHandlerMapping mapping = new DelegatingHandlerMapping(Arrays.asList(first, second));
|
||||
DelegatingHandlerMapping mapping = new DelegatingHandlerMapping(Arrays.asList(first, second), null);
|
||||
|
||||
assertHandlerTriedButExceptionThrown(mapping, HttpMediaTypeNotAcceptableException.class);
|
||||
assertHandlerTriedButExceptionThrown(mapping, HttpRequestMethodNotSupportedException.class);
|
||||
@@ -73,7 +73,7 @@ public class DelegatingHandlerMappingUnitTests {
|
||||
MatchableHandlerMapping fourth = mock(MatchableHandlerMapping.class, Answers.RETURNS_MOCKS);
|
||||
doReturn(result).when(fourth).match(any(), any(String.class));
|
||||
|
||||
DelegatingHandlerMapping mapping = new DelegatingHandlerMapping(Arrays.asList(first, second, third, fourth));
|
||||
DelegatingHandlerMapping mapping = new DelegatingHandlerMapping(Arrays.asList(first, second, third, fourth), null);
|
||||
|
||||
assertThat(mapping.match(request, "somePattern")).isEqualTo(result);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user