Skip to content

Commit b0e8e7f

Browse files
committed
Refactor MappingContentTypeResolver implementations
After the removal of suffix pattern matches, there is no longer a need to expose the list of registered file extensions. Also polish, refactor, and simplify the abstract base class AbstractMappingContentTypeResolver and its sub-classes. Issue: SPR-15639
1 parent cb60473 commit b0e8e7f

13 files changed

+73
-467
lines changed
Lines changed: 22 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,148 +15,68 @@
1515
*/
1616
package org.springframework.web.reactive.accept;
1717

18-
import java.util.ArrayList;
1918
import java.util.Collections;
20-
import java.util.HashSet;
2119
import java.util.List;
2220
import java.util.Locale;
2321
import java.util.Map;
24-
import java.util.Set;
2522
import java.util.concurrent.ConcurrentHashMap;
2623

2724
import org.springframework.http.MediaType;
25+
import org.springframework.http.MediaTypeFactory;
2826
import org.springframework.lang.Nullable;
29-
import org.springframework.util.LinkedMultiValueMap;
30-
import org.springframework.util.MultiValueMap;
3127
import org.springframework.util.StringUtils;
32-
import org.springframework.web.server.NotAcceptableStatusException;
3328
import org.springframework.web.server.ServerWebExchange;
3429

3530
/**
36-
* Abstract base class for {@link MappingContentTypeResolver} implementations.
37-
* Maintains the actual mappings and pre-implements the overall algorithm with
38-
* sub-classes left to provide a way to extract the lookup key (e.g. file
39-
* extension, query parameter, etc) for a given exchange.
31+
* Base class for resolvers that extract a key from the request and look up a
32+
* mapping to a MediaType. The use case is URI-based content negotiation for
33+
* example based on query parameter or file extension in the request path.
4034
*
4135
* @author Rossen Stoyanchev
4236
* @since 5.0
4337
*/
44-
public abstract class AbstractMappingContentTypeResolver implements MappingContentTypeResolver {
38+
public abstract class AbstractMappingContentTypeResolver implements RequestedContentTypeResolver {
4539

4640
/** Primary lookup for media types by key (e.g. "json" -> "application/json") */
4741
private final Map<String, MediaType> mediaTypeLookup = new ConcurrentHashMap<>(64);
4842

49-
/** Reverse lookup for keys associated with a media type */
50-
private final MultiValueMap<MediaType, String> keyLookup = new LinkedMultiValueMap<>(64);
5143

52-
53-
/**
54-
* Create an instance with the given map of file extensions and media types.
55-
*/
56-
public AbstractMappingContentTypeResolver(@Nullable Map<String, MediaType> mediaTypes) {
57-
if (mediaTypes != null) {
58-
for (Map.Entry<String, MediaType> entry : mediaTypes.entrySet()) {
59-
String extension = entry.getKey().toLowerCase(Locale.ENGLISH);
60-
MediaType mediaType = entry.getValue();
61-
this.mediaTypeLookup.put(extension, mediaType);
62-
this.keyLookup.add(mediaType, extension);
63-
}
64-
}
65-
}
66-
67-
68-
public Map<String, MediaType> getMediaTypes() {
69-
return this.mediaTypeLookup;
70-
}
71-
72-
/**
73-
* Sub-classes can use this method to look up a MediaType by key.
74-
* @param key the key converted to lower case
75-
* @return a MediaType or {@code null}
76-
*/
77-
@Nullable
78-
protected MediaType getMediaType(String key) {
79-
return this.mediaTypeLookup.get(key.toLowerCase(Locale.ENGLISH));
80-
}
81-
82-
/**
83-
* Sub-classes can use this method get all mapped media types.
84-
*/
85-
protected List<MediaType> getAllMediaTypes() {
86-
return new ArrayList<>(this.mediaTypeLookup.values());
44+
public AbstractMappingContentTypeResolver(Map<String, MediaType> mediaTypes) {
45+
mediaTypes.forEach((key, mediaType) ->
46+
this.mediaTypeLookup.put(key.toLowerCase(Locale.ENGLISH), mediaType));
8747
}
8848

8949

90-
// RequestedContentTypeResolver implementation
91-
9250
@Override
93-
public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) throws NotAcceptableStatusException {
94-
return resolveMediaTypes(extractKey(exchange));
95-
}
96-
97-
/**
98-
* An overloaded resolve method with a pre-resolved lookup key.
99-
* @param key the key for looking up media types
100-
* @return a list of resolved media types or an empty list
101-
* @throws NotAcceptableStatusException
102-
*/
103-
public List<MediaType> resolveMediaTypes(@Nullable String key) throws NotAcceptableStatusException {
51+
public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) {
52+
String key = getKey(exchange);
10453
if (StringUtils.hasText(key)) {
10554
MediaType mediaType = getMediaType(key);
10655
if (mediaType != null) {
107-
handleMatch(key, mediaType);
108-
return Collections.singletonList(mediaType);
109-
}
110-
mediaType = handleNoMatch(key);
111-
if (mediaType != null) {
112-
MediaType previous = this.mediaTypeLookup.putIfAbsent(key, mediaType);
113-
if (previous == null) {
114-
this.keyLookup.add(mediaType, key);
115-
}
56+
this.mediaTypeLookup.putIfAbsent(key, mediaType);
11657
return Collections.singletonList(mediaType);
11758
}
11859
}
11960
return Collections.emptyList();
12061
}
12162

12263
/**
123-
* Extract the key to use to look up a media type from the given exchange,
124-
* e.g. file extension, query parameter, etc.
125-
* @return the key or {@code null}
64+
* Get the key to look up a MediaType with.
12665
*/
12766
@Nullable
128-
protected abstract String extractKey(ServerWebExchange exchange);
129-
130-
/**
131-
* Override to provide handling when a key is successfully resolved via
132-
* {@link #getMediaType(String)}.
133-
*/
134-
@SuppressWarnings("UnusedParameters")
135-
protected void handleMatch(String key, MediaType mediaType) {
136-
}
67+
protected abstract String getKey(ServerWebExchange exchange);
13768

13869
/**
139-
* Override to provide handling when a key is not resolved via.
140-
* {@link #getMediaType(String)}. If a MediaType is returned from
141-
* this method it will be added to the mappings.
70+
* Get the MediaType for the given key.
14271
*/
143-
@SuppressWarnings("UnusedParameters")
14472
@Nullable
145-
protected MediaType handleNoMatch(String key) throws NotAcceptableStatusException {
146-
return null;
147-
}
148-
149-
// MappingContentTypeResolver implementation
150-
151-
@Override
152-
public Set<String> getKeysFor(MediaType mediaType) {
153-
List<String> keys = this.keyLookup.get(mediaType);
154-
return (keys != null ? new HashSet<>(keys) : Collections.emptySet());
155-
}
156-
157-
@Override
158-
public Set<String> getKeys() {
159-
return new HashSet<>(this.mediaTypeLookup.keySet());
73+
protected MediaType getMediaType(String key) {
74+
key = key.toLowerCase(Locale.ENGLISH);
75+
MediaType mediaType = this.mediaTypeLookup.get(key);
76+
if (mediaType == null) {
77+
mediaType = MediaTypeFactory.getMediaType("filename." + key).orElse(null);
78+
}
79+
return mediaType;
16080
}
16181

16282
}
Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,28 +17,19 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.Collections;
20-
import java.util.LinkedHashSet;
2120
import java.util.List;
22-
import java.util.Set;
2321

2422
import org.springframework.http.MediaType;
25-
import org.springframework.lang.Nullable;
2623
import org.springframework.util.Assert;
27-
import org.springframework.web.server.NotAcceptableStatusException;
2824
import org.springframework.web.server.ServerWebExchange;
2925

3026
/**
31-
* A {@link RequestedContentTypeResolver} that contains and delegates to a list of other
32-
* resolvers.
33-
*
34-
* <p>Also an implementation of {@link MappingContentTypeResolver} that delegates
35-
* to those resolvers from the list that are also of type
36-
* {@code MappingContentTypeResolver}.
27+
* Contains and delegates to other {@link RequestedContentTypeResolver}.
3728
*
3829
* @author Rossen Stoyanchev
3930
* @since 5.0
4031
*/
41-
public class CompositeContentTypeResolver implements MappingContentTypeResolver {
32+
public class CompositeContentTypeResolver implements RequestedContentTypeResolver {
4233

4334
private final List<RequestedContentTypeResolver> resolvers = new ArrayList<>();
4435

@@ -49,32 +40,8 @@ public CompositeContentTypeResolver(List<RequestedContentTypeResolver> resolvers
4940
}
5041

5142

52-
/**
53-
* Return a read-only list of the configured resolvers.
54-
*/
55-
public List<RequestedContentTypeResolver> getResolvers() {
56-
return Collections.unmodifiableList(this.resolvers);
57-
}
58-
59-
/**
60-
* Return the first {@link RequestedContentTypeResolver} of the given type.
61-
* @param resolverType the resolver type
62-
* @return the first matching resolver or {@code null}.
63-
*/
64-
@SuppressWarnings("unchecked")
65-
@Nullable
66-
public <T extends RequestedContentTypeResolver> T findResolver(Class<T> resolverType) {
67-
for (RequestedContentTypeResolver resolver : this.resolvers) {
68-
if (resolverType.isInstance(resolver)) {
69-
return (T) resolver;
70-
}
71-
}
72-
return null;
73-
}
74-
75-
7643
@Override
77-
public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) throws NotAcceptableStatusException {
44+
public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) {
7845
for (RequestedContentTypeResolver resolver : this.resolvers) {
7946
List<MediaType> mediaTypes = resolver.resolveMediaTypes(exchange);
8047
if (mediaTypes.isEmpty() || (mediaTypes.size() == 1 && mediaTypes.contains(MediaType.ALL))) {
@@ -85,24 +52,4 @@ public List<MediaType> resolveMediaTypes(ServerWebExchange exchange) throws NotA
8552
return Collections.emptyList();
8653
}
8754

88-
@Override
89-
public Set<String> getKeysFor(MediaType mediaType) {
90-
Set<String> result = new LinkedHashSet<>();
91-
for (RequestedContentTypeResolver resolver : this.resolvers) {
92-
if (resolver instanceof MappingContentTypeResolver)
93-
result.addAll(((MappingContentTypeResolver) resolver).getKeysFor(mediaType));
94-
}
95-
return result;
96-
}
97-
98-
@Override
99-
public Set<String> getKeys() {
100-
Set<String> result = new LinkedHashSet<>();
101-
for (RequestedContentTypeResolver resolver : this.resolvers) {
102-
if (resolver instanceof MappingContentTypeResolver)
103-
result.addAll(((MappingContentTypeResolver) resolver).getKeys());
104-
}
105-
return result;
106-
}
107-
10855
}

spring-webflux/src/main/java/org/springframework/web/reactive/accept/MappingContentTypeResolver.java

Lines changed: 0 additions & 46 deletions
This file was deleted.

spring-webflux/src/main/java/org/springframework/web/reactive/accept/ParameterContentTypeResolver.java

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,21 @@
1818

1919
import java.util.Map;
2020

21-
import org.apache.commons.logging.Log;
22-
import org.apache.commons.logging.LogFactory;
23-
2421
import org.springframework.http.MediaType;
2522
import org.springframework.util.Assert;
26-
import org.springframework.web.server.NotAcceptableStatusException;
2723
import org.springframework.web.server.ServerWebExchange;
2824

2925
/**
30-
* A {@link RequestedContentTypeResolver} that extracts the media type lookup
31-
* key from a known query parameter named "format" by default.
26+
* Query parameter based {@link AbstractMappingContentTypeResolver}.
3227
*
3328
* @author Rossen Stoyanchev
3429
* @since 5.0
3530
*/
3631
public class ParameterContentTypeResolver extends AbstractMappingContentTypeResolver {
3732

38-
private static final Log logger = LogFactory.getLog(ParameterContentTypeResolver.class);
39-
4033
private String parameterName = "format";
4134

4235

43-
/**
44-
* Create an instance with the given map of file extensions and media types.
45-
*/
4636
public ParameterContentTypeResolver(Map<String, MediaType> mediaTypes) {
4737
super(mediaTypes);
4838
}
@@ -53,7 +43,7 @@ public ParameterContentTypeResolver(Map<String, MediaType> mediaTypes) {
5343
* <p>By default this is set to {@code "format"}.
5444
*/
5545
public void setParameterName(String parameterName) {
56-
Assert.notNull(parameterName, "parameterName is required");
46+
Assert.notNull(parameterName, "'parameterName' is required");
5747
this.parameterName = parameterName;
5848
}
5949

@@ -63,21 +53,8 @@ public String getParameterName() {
6353

6454

6555
@Override
66-
protected String extractKey(ServerWebExchange exchange) {
56+
protected String getKey(ServerWebExchange exchange) {
6757
return exchange.getRequest().getQueryParams().getFirst(getParameterName());
6858
}
6959

70-
@Override
71-
protected void handleMatch(String mediaTypeKey, MediaType mediaType) {
72-
if (logger.isDebugEnabled()) {
73-
logger.debug("Requested media type is '" + mediaType +
74-
"' based on '" + getParameterName() + "'='" + mediaTypeKey + "'.");
75-
}
76-
}
77-
78-
@Override
79-
protected MediaType handleNoMatch(String key) throws NotAcceptableStatusException {
80-
throw new NotAcceptableStatusException(getAllMediaTypes());
81-
}
82-
8360
}

0 commit comments

Comments
 (0)