001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.util;
018
019import java.net.URI;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.Map;
024
025import org.springframework.lang.Nullable;
026import org.springframework.util.MultiValueMap;
027import org.springframework.util.ObjectUtils;
028import org.springframework.util.StringUtils;
029
030/**
031 * {@code UriBuilderFactory} that relies on {@link UriComponentsBuilder} for
032 * the actual building of the URI.
033 *
034 * <p>Provides options to create {@link UriBuilder} instances with a common
035 * base URI, alternative encoding mode strategies, among others.
036 *
037 * @author Rossen Stoyanchev
038 * @since 5.0
039 * @see UriComponentsBuilder
040 */
041public class DefaultUriBuilderFactory implements UriBuilderFactory {
042
043        @Nullable
044        private final UriComponentsBuilder baseUri;
045
046        private EncodingMode encodingMode = EncodingMode.TEMPLATE_AND_VALUES;
047
048        private final Map<String, Object> defaultUriVariables = new HashMap<>();
049
050        private boolean parsePath = true;
051
052
053        /**
054         * Default constructor without a base URI.
055         * <p>The target address must be specified on each UriBuilder.
056         */
057        public DefaultUriBuilderFactory() {
058                this.baseUri = null;
059        }
060
061        /**
062         * Constructor with a base URI.
063         * <p>The given URI template is parsed via
064         * {@link UriComponentsBuilder#fromUriString} and then applied as a base URI
065         * to every UriBuilder via {@link UriComponentsBuilder#uriComponents} unless
066         * the UriBuilder itself was created with a URI template that already has a
067         * target address.
068         * @param baseUriTemplate the URI template to use a base URL
069         */
070        public DefaultUriBuilderFactory(String baseUriTemplate) {
071                this.baseUri = UriComponentsBuilder.fromUriString(baseUriTemplate);
072        }
073
074        /**
075         * Variant of {@link #DefaultUriBuilderFactory(String)} with a
076         * {@code UriComponentsBuilder}.
077         */
078        public DefaultUriBuilderFactory(UriComponentsBuilder baseUri) {
079                this.baseUri = baseUri;
080        }
081
082
083        /**
084         * Set the {@link EncodingMode encoding mode} to use.
085         * <p>By default this is set to {@link EncodingMode#TEMPLATE_AND_VALUES
086         * EncodingMode.TEMPLATE_AND_VALUES}.
087         * <p><strong>Note:</strong> Prior to 5.1 the default was
088         * {@link EncodingMode#URI_COMPONENT EncodingMode.URI_COMPONENT}
089         * therefore the {@code WebClient} {@code RestTemplate} have switched their
090         * default behavior.
091         * @param encodingMode the encoding mode to use
092         */
093        public void setEncodingMode(EncodingMode encodingMode) {
094                this.encodingMode = encodingMode;
095        }
096
097        /**
098         * Return the configured encoding mode.
099         */
100        public EncodingMode getEncodingMode() {
101                return this.encodingMode;
102        }
103
104        /**
105         * Provide default URI variable values to use when expanding URI templates
106         * with a Map of variables.
107         * @param defaultUriVariables default URI variable values
108         */
109        public void setDefaultUriVariables(@Nullable Map<String, ?> defaultUriVariables) {
110                this.defaultUriVariables.clear();
111                if (defaultUriVariables != null) {
112                        this.defaultUriVariables.putAll(defaultUriVariables);
113                }
114        }
115
116        /**
117         * Return the configured default URI variable values.
118         */
119        public Map<String, ?> getDefaultUriVariables() {
120                return Collections.unmodifiableMap(this.defaultUriVariables);
121        }
122
123        /**
124         * Whether to parse the input path into path segments if the encoding mode
125         * is set to {@link EncodingMode#URI_COMPONENT EncodingMode.URI_COMPONENT},
126         * which ensures that URI variables in the path are encoded according to
127         * path segment rules and for example a '/' is encoded.
128         * <p>By default this is set to {@code true}.
129         * @param parsePath whether to parse the path into path segments
130         */
131        public void setParsePath(boolean parsePath) {
132                this.parsePath = parsePath;
133        }
134
135        /**
136         * Whether to parse the path into path segments if the encoding mode is set
137         * to {@link EncodingMode#URI_COMPONENT EncodingMode.URI_COMPONENT}.
138         */
139        public boolean shouldParsePath() {
140                return this.parsePath;
141        }
142
143
144        // UriTemplateHandler
145
146        @Override
147        public URI expand(String uriTemplate, Map<String, ?> uriVars) {
148                return uriString(uriTemplate).build(uriVars);
149        }
150
151        @Override
152        public URI expand(String uriTemplate, Object... uriVars) {
153                return uriString(uriTemplate).build(uriVars);
154        }
155
156        // UriBuilderFactory
157
158        @Override
159        public UriBuilder uriString(String uriTemplate) {
160                return new DefaultUriBuilder(uriTemplate);
161        }
162
163        @Override
164        public UriBuilder builder() {
165                return new DefaultUriBuilder("");
166        }
167
168
169        /**
170         * Enum to represent multiple URI encoding strategies. The following are
171         * available:
172         * <ul>
173         * <li>{@link #TEMPLATE_AND_VALUES}
174         * <li>{@link #VALUES_ONLY}
175         * <li>{@link #URI_COMPONENT}
176         * <li>{@link #NONE}
177         * </ul>
178         * @see #setEncodingMode
179         */
180        public enum EncodingMode {
181
182                /**
183                 * Pre-encode the URI template first, then strictly encode URI variables
184                 * when expanded, with the following rules:
185                 * <ul>
186                 * <li>For the URI template replace <em>only</em> non-ASCII and illegal
187                 * (within a given URI component type) characters with escaped octets.
188                 * <li>For URI variables do the same and also replace characters with
189                 * reserved meaning.
190                 * </ul>
191                 * <p>For most cases, this mode is most likely to give the expected
192                 * result because in treats URI variables as opaque data to be fully
193                 * encoded, while {@link #URI_COMPONENT} by comparison is useful only
194                 * if intentionally expanding URI variables with reserved characters.
195                 * @since 5.0.8
196                 * @see UriComponentsBuilder#encode()
197                 */
198                TEMPLATE_AND_VALUES,
199
200                /**
201                 * Does not encode the URI template and instead applies strict encoding
202                 * to URI variables via {@link UriUtils#encodeUriVariables} prior to
203                 * expanding them into the template.
204                 * @see UriUtils#encodeUriVariables(Object...)
205                 * @see UriUtils#encodeUriVariables(Map)
206                 */
207                VALUES_ONLY,
208
209                /**
210                 * Expand URI variables first, and then encode the resulting URI
211                 * component values, replacing <em>only</em> non-ASCII and illegal
212                 * (within a given URI component type) characters, but not characters
213                 * with reserved meaning.
214                 * @see UriComponents#encode()
215                 */
216                URI_COMPONENT,
217
218                /**
219                 * No encoding should be applied.
220                 */
221                NONE
222        }
223
224
225        /**
226         * {@link DefaultUriBuilderFactory} specific implementation of UriBuilder.
227         */
228        private class DefaultUriBuilder implements UriBuilder {
229
230                private final UriComponentsBuilder uriComponentsBuilder;
231
232                public DefaultUriBuilder(String uriTemplate) {
233                        this.uriComponentsBuilder = initUriComponentsBuilder(uriTemplate);
234                }
235
236                private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
237                        UriComponentsBuilder result;
238                        if (!StringUtils.hasLength(uriTemplate)) {
239                                result = (baseUri != null ? baseUri.cloneBuilder() : UriComponentsBuilder.newInstance());
240                        }
241                        else if (baseUri != null) {
242                                UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uriTemplate);
243                                UriComponents uri = builder.build();
244                                result = (uri.getHost() == null ? baseUri.cloneBuilder().uriComponents(uri) : builder);
245                        }
246                        else {
247                                result = UriComponentsBuilder.fromUriString(uriTemplate);
248                        }
249                        if (encodingMode.equals(EncodingMode.TEMPLATE_AND_VALUES)) {
250                                result.encode();
251                        }
252                        parsePathIfNecessary(result);
253                        return result;
254                }
255
256                private void parsePathIfNecessary(UriComponentsBuilder result) {
257                        if (parsePath && encodingMode.equals(EncodingMode.URI_COMPONENT)) {
258                                UriComponents uric = result.build();
259                                String path = uric.getPath();
260                                result.replacePath(null);
261                                for (String segment : uric.getPathSegments()) {
262                                        result.pathSegment(segment);
263                                }
264                                if (path != null && path.endsWith("/")) {
265                                        result.path("/");
266                                }
267                        }
268                }
269
270
271                @Override
272                public DefaultUriBuilder scheme(@Nullable String scheme) {
273                        this.uriComponentsBuilder.scheme(scheme);
274                        return this;
275                }
276
277                @Override
278                public DefaultUriBuilder userInfo(@Nullable String userInfo) {
279                        this.uriComponentsBuilder.userInfo(userInfo);
280                        return this;
281                }
282
283                @Override
284                public DefaultUriBuilder host(@Nullable String host) {
285                        this.uriComponentsBuilder.host(host);
286                        return this;
287                }
288
289                @Override
290                public DefaultUriBuilder port(int port) {
291                        this.uriComponentsBuilder.port(port);
292                        return this;
293                }
294
295                @Override
296                public DefaultUriBuilder port(@Nullable String port) {
297                        this.uriComponentsBuilder.port(port);
298                        return this;
299                }
300
301                @Override
302                public DefaultUriBuilder path(String path) {
303                        this.uriComponentsBuilder.path(path);
304                        return this;
305                }
306
307                @Override
308                public DefaultUriBuilder replacePath(@Nullable String path) {
309                        this.uriComponentsBuilder.replacePath(path);
310                        return this;
311                }
312
313                @Override
314                public DefaultUriBuilder pathSegment(String... pathSegments) {
315                        this.uriComponentsBuilder.pathSegment(pathSegments);
316                        return this;
317                }
318
319                @Override
320                public DefaultUriBuilder query(String query) {
321                        this.uriComponentsBuilder.query(query);
322                        return this;
323                }
324
325                @Override
326                public DefaultUriBuilder replaceQuery(@Nullable String query) {
327                        this.uriComponentsBuilder.replaceQuery(query);
328                        return this;
329                }
330
331                @Override
332                public DefaultUriBuilder queryParam(String name, Object... values) {
333                        this.uriComponentsBuilder.queryParam(name, values);
334                        return this;
335                }
336
337                @Override
338                public DefaultUriBuilder queryParam(String name, @Nullable Collection<?> values) {
339                        this.uriComponentsBuilder.queryParam(name, values);
340                        return this;
341                }
342
343                @Override
344                public DefaultUriBuilder replaceQueryParam(String name, Object... values) {
345                        this.uriComponentsBuilder.replaceQueryParam(name, values);
346                        return this;
347                }
348
349                @Override
350                public DefaultUriBuilder replaceQueryParam(String name, @Nullable Collection<?> values) {
351                        this.uriComponentsBuilder.replaceQueryParam(name, values);
352                        return this;
353                }
354
355                @Override
356                public DefaultUriBuilder queryParams(MultiValueMap<String, String> params) {
357                        this.uriComponentsBuilder.queryParams(params);
358                        return this;
359                }
360
361                @Override
362                public DefaultUriBuilder replaceQueryParams(MultiValueMap<String, String> params) {
363                        this.uriComponentsBuilder.replaceQueryParams(params);
364                        return this;
365                }
366
367                @Override
368                public DefaultUriBuilder fragment(@Nullable String fragment) {
369                        this.uriComponentsBuilder.fragment(fragment);
370                        return this;
371                }
372
373                @Override
374                public URI build(Map<String, ?> uriVars) {
375                        if (!defaultUriVariables.isEmpty()) {
376                                Map<String, Object> map = new HashMap<>();
377                                map.putAll(defaultUriVariables);
378                                map.putAll(uriVars);
379                                uriVars = map;
380                        }
381                        if (encodingMode.equals(EncodingMode.VALUES_ONLY)) {
382                                uriVars = UriUtils.encodeUriVariables(uriVars);
383                        }
384                        UriComponents uric = this.uriComponentsBuilder.build().expand(uriVars);
385                        return createUri(uric);
386                }
387
388                @Override
389                public URI build(Object... uriVars) {
390                        if (ObjectUtils.isEmpty(uriVars) && !defaultUriVariables.isEmpty()) {
391                                return build(Collections.emptyMap());
392                        }
393                        if (encodingMode.equals(EncodingMode.VALUES_ONLY)) {
394                                uriVars = UriUtils.encodeUriVariables(uriVars);
395                        }
396                        UriComponents uric = this.uriComponentsBuilder.build().expand(uriVars);
397                        return createUri(uric);
398                }
399
400                private URI createUri(UriComponents uric) {
401                        if (encodingMode.equals(EncodingMode.URI_COMPONENT)) {
402                                uric = uric.encode();
403                        }
404                        return URI.create(uric.toString());
405                }
406        }
407
408}