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:
Oliver Drotbohm
2021-01-26 00:00:11 +01:00
parent bc3f3c1602
commit 531ad91ad0
5 changed files with 51 additions and 31 deletions

View File

@@ -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();
}
/**

View File

@@ -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();
}
/**

View File

@@ -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")

View File

@@ -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

View File

@@ -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);
}