|
1 | 1 | /*
|
2 |
| - * Copyright 2002-2016 the original author or authors. |
| 2 | + * Copyright 2002-2017 the original author or authors. |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * you may not use this file except in compliance with the License.
|
|
15 | 15 | */
|
16 | 16 | package org.springframework.web.reactive.accept;
|
17 | 17 |
|
18 |
| -import java.util.ArrayList; |
19 | 18 | import java.util.Collections;
|
20 |
| -import java.util.HashSet; |
21 | 19 | import java.util.List;
|
22 | 20 | import java.util.Locale;
|
23 | 21 | import java.util.Map;
|
24 |
| -import java.util.Set; |
25 | 22 | import java.util.concurrent.ConcurrentHashMap;
|
26 | 23 |
|
27 | 24 | import org.springframework.http.MediaType;
|
| 25 | +import org.springframework.http.MediaTypeFactory; |
28 | 26 | import org.springframework.lang.Nullable;
|
29 |
| -import org.springframework.util.LinkedMultiValueMap; |
30 |
| -import org.springframework.util.MultiValueMap; |
31 | 27 | import org.springframework.util.StringUtils;
|
32 |
| -import org.springframework.web.server.NotAcceptableStatusException; |
33 | 28 | import org.springframework.web.server.ServerWebExchange;
|
34 | 29 |
|
35 | 30 | /**
|
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. |
40 | 34 | *
|
41 | 35 | * @author Rossen Stoyanchev
|
42 | 36 | * @since 5.0
|
43 | 37 | */
|
44 |
| -public abstract class AbstractMappingContentTypeResolver implements MappingContentTypeResolver { |
| 38 | +public abstract class AbstractMappingContentTypeResolver implements RequestedContentTypeResolver { |
45 | 39 |
|
46 | 40 | /** Primary lookup for media types by key (e.g. "json" -> "application/json") */
|
47 | 41 | private final Map<String, MediaType> mediaTypeLookup = new ConcurrentHashMap<>(64);
|
48 | 42 |
|
49 |
| - /** Reverse lookup for keys associated with a media type */ |
50 |
| - private final MultiValueMap<MediaType, String> keyLookup = new LinkedMultiValueMap<>(64); |
51 | 43 |
|
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)); |
87 | 47 | }
|
88 | 48 |
|
89 | 49 |
|
90 |
| - // RequestedContentTypeResolver implementation |
91 |
| - |
92 | 50 | @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); |
104 | 53 | if (StringUtils.hasText(key)) {
|
105 | 54 | MediaType mediaType = getMediaType(key);
|
106 | 55 | 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); |
116 | 57 | return Collections.singletonList(mediaType);
|
117 | 58 | }
|
118 | 59 | }
|
119 | 60 | return Collections.emptyList();
|
120 | 61 | }
|
121 | 62 |
|
122 | 63 | /**
|
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. |
126 | 65 | */
|
127 | 66 | @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); |
137 | 68 |
|
138 | 69 | /**
|
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. |
142 | 71 | */
|
143 |
| - @SuppressWarnings("UnusedParameters") |
144 | 72 | @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; |
160 | 80 | }
|
161 | 81 |
|
162 | 82 | }
|
0 commit comments