/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.util;

import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.springframework.lang.Nullable;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

UriBuilderFactory that relies on UriComponentsBuilder for the actual building of the URI.

Provides options to create UriBuilder instances with a common base URI, alternative encoding mode strategies, among others.

Author:Rossen Stoyanchev
See Also:
Since:5.0
/** * {@code UriBuilderFactory} that relies on {@link UriComponentsBuilder} for * the actual building of the URI. * * <p>Provides options to create {@link UriBuilder} instances with a common * base URI, alternative encoding mode strategies, among others. * * * @author Rossen Stoyanchev * @since 5.0 * @see UriComponentsBuilder */
public class DefaultUriBuilderFactory implements UriBuilderFactory {
Enum to represent multiple URI encoding strategies.
See Also:
  • setEncodingMode
/** * Enum to represent multiple URI encoding strategies. * @see #setEncodingMode */
public enum EncodingMode {
Pre-encode the URI template first, then strictly encode URI variables when expanded, with the following rules:
  • For the URI template replace only non-ASCII and illegal (within a given URI component type) characters with escaped octets.
  • For URI variables do the same and also replace characters with reserved meaning.

For most cases, this mode is most likely to give the expected result because in treats URI variables as opaque data to be fully encoded, while URI_COMPONENT by comparison is useful only if intentionally expanding URI variables with reserved characters.

See Also:
Since:5.0.8
/** * Pre-encode the URI template first, then strictly encode URI variables * when expanded, with the following rules: * <ul> * <li>For the URI template replace <em>only</em> non-ASCII and illegal * (within a given URI component type) characters with escaped octets. * <li>For URI variables do the same and also replace characters with * reserved meaning. * </ul> * <p>For most cases, this mode is most likely to give the expected * result because in treats URI variables as opaque data to be fully * encoded, while {@link #URI_COMPONENT} by comparison is useful only * if intentionally expanding URI variables with reserved characters. * @since 5.0.8 * @see UriComponentsBuilder#encode() */
TEMPLATE_AND_VALUES,
Does not encode the URI template and instead applies strict encoding to URI variables via UriUtils.encodeUriVariables prior to expanding them into the template.
See Also:
/** * Does not encode the URI template and instead applies strict encoding * to URI variables via {@link UriUtils#encodeUriVariables} prior to * expanding them into the template. * @see UriUtils#encodeUriVariables(Object...) * @see UriUtils#encodeUriVariables(Map) */
VALUES_ONLY,
Expand URI variables first, and then encode the resulting URI component values, replacing only non-ASCII and illegal (within a given URI component type) characters, but not characters with reserved meaning.
See Also:
  • encode.encode()
/** * Expand URI variables first, and then encode the resulting URI * component values, replacing <em>only</em> non-ASCII and illegal * (within a given URI component type) characters, but not characters * with reserved meaning. * @see UriComponents#encode() */
URI_COMPONENT,
No encoding should be applied.
/** * No encoding should be applied. */
NONE } @Nullable private final UriComponentsBuilder baseUri; private EncodingMode encodingMode = EncodingMode.TEMPLATE_AND_VALUES; private final Map<String, Object> defaultUriVariables = new HashMap<>(); private boolean parsePath = true;
Default constructor without a base URI.

The target address must be specified on each UriBuilder.

/** * Default constructor without a base URI. * <p>The target address must be specified on each UriBuilder. */
public DefaultUriBuilderFactory() { this.baseUri = null; }
Constructor with a base URI.

The given URI template is parsed via UriComponentsBuilder.fromUriString and then applied as a base URI to every UriBuilder via UriComponentsBuilder.uriComponents unless the UriBuilder itself was created with a URI template that already has a target address.

Params:
  • baseUriTemplate – the URI template to use a base URL
/** * Constructor with a base URI. * <p>The given URI template is parsed via * {@link UriComponentsBuilder#fromUriString} and then applied as a base URI * to every UriBuilder via {@link UriComponentsBuilder#uriComponents} unless * the UriBuilder itself was created with a URI template that already has a * target address. * @param baseUriTemplate the URI template to use a base URL */
public DefaultUriBuilderFactory(String baseUriTemplate) { this.baseUri = UriComponentsBuilder.fromUriString(baseUriTemplate); }
Variant of DefaultUriBuilderFactory(String) with a UriComponentsBuilder.
/** * Variant of {@link #DefaultUriBuilderFactory(String)} with a * {@code UriComponentsBuilder}. */
public DefaultUriBuilderFactory(UriComponentsBuilder baseUri) { this.baseUri = baseUri; }
Set the encoding mode to use.

By default this is set to EncodingMode.TEMPLATE_AND_VALUES.

Note: In 5.1 the default was changed from EncodingMode.URI_COMPONENT. Consequently the WebClient, which relies on the built-in default has also been switched to the new default. The RestTemplate however sets this explicitly to EncodingMode.URI_COMPONENT explicitly for historic and backwards compatibility reasons.

Params:
  • encodingMode – the encoding mode to use
/** * Set the encoding mode to use. * <p>By default this is set to {@link EncodingMode#TEMPLATE_AND_VALUES * EncodingMode.TEMPLATE_AND_VALUES}. * <p><strong>Note:</strong> In 5.1 the default was changed from * {@link EncodingMode#URI_COMPONENT EncodingMode.URI_COMPONENT}. * Consequently the {@code WebClient}, which relies on the built-in default * has also been switched to the new default. The {@code RestTemplate} * however sets this explicitly to {@link EncodingMode#URI_COMPONENT * EncodingMode.URI_COMPONENT} explicitly for historic and backwards * compatibility reasons. * @param encodingMode the encoding mode to use */
public void setEncodingMode(EncodingMode encodingMode) { this.encodingMode = encodingMode; }
Return the configured encoding mode.
/** * Return the configured encoding mode. */
public EncodingMode getEncodingMode() { return this.encodingMode; }
Provide default URI variable values to use when expanding URI templates with a Map of variables.
Params:
  • defaultUriVariables – default URI variable values
/** * Provide default URI variable values to use when expanding URI templates * with a Map of variables. * @param defaultUriVariables default URI variable values */
public void setDefaultUriVariables(@Nullable Map<String, ?> defaultUriVariables) { this.defaultUriVariables.clear(); if (defaultUriVariables != null) { this.defaultUriVariables.putAll(defaultUriVariables); } }
Return the configured default URI variable values.
/** * Return the configured default URI variable values. */
public Map<String, ?> getDefaultUriVariables() { return Collections.unmodifiableMap(this.defaultUriVariables); }
Whether to parse the input path into path segments if the encoding mode is set to EncodingMode.URI_COMPONENT, which ensures that URI variables in the path are encoded according to path segment rules and for example a '/' is encoded.

By default this is set to true.

Params:
  • parsePath – whether to parse the path into path segments
/** * Whether to parse the input path into path segments if the encoding mode * is set to {@link EncodingMode#URI_COMPONENT EncodingMode.URI_COMPONENT}, * which ensures that URI variables in the path are encoded according to * path segment rules and for example a '/' is encoded. * <p>By default this is set to {@code true}. * @param parsePath whether to parse the path into path segments */
public void setParsePath(boolean parsePath) { this.parsePath = parsePath; }
Whether to parse the path into path segments if the encoding mode is set to EncodingMode.URI_COMPONENT.
/** * Whether to parse the path into path segments if the encoding mode is set * to {@link EncodingMode#URI_COMPONENT EncodingMode.URI_COMPONENT}. */
public boolean shouldParsePath() { return this.parsePath; } // UriTemplateHandler public URI expand(String uriTemplate, Map<String, ?> uriVars) { return uriString(uriTemplate).build(uriVars); } public URI expand(String uriTemplate, Object... uriVars) { return uriString(uriTemplate).build(uriVars); } // UriBuilderFactory public UriBuilder uriString(String uriTemplate) { return new DefaultUriBuilder(uriTemplate); } @Override public UriBuilder builder() { return new DefaultUriBuilder(""); }
DefaultUriBuilderFactory specific implementation of UriBuilder.
/** * {@link DefaultUriBuilderFactory} specific implementation of UriBuilder. */
private class DefaultUriBuilder implements UriBuilder { private final UriComponentsBuilder uriComponentsBuilder; public DefaultUriBuilder(String uriTemplate) { this.uriComponentsBuilder = initUriComponentsBuilder(uriTemplate); } private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) { UriComponentsBuilder result; if (StringUtils.isEmpty(uriTemplate)) { result = baseUri != null ? baseUri.cloneBuilder() : UriComponentsBuilder.newInstance(); } else if (baseUri != null) { UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uriTemplate); UriComponents uri = builder.build(); result = uri.getHost() == null ? baseUri.cloneBuilder().uriComponents(uri) : builder; } else { result = UriComponentsBuilder.fromUriString(uriTemplate); } if (encodingMode.equals(EncodingMode.TEMPLATE_AND_VALUES)) { result.encode(); } parsePathIfNecessary(result); return result; } private void parsePathIfNecessary(UriComponentsBuilder result) { if (parsePath && encodingMode.equals(EncodingMode.URI_COMPONENT)) { UriComponents uric = result.build(); String path = uric.getPath(); result.replacePath(null); for (String segment : uric.getPathSegments()) { result.pathSegment(segment); } if (path != null && path.endsWith("/")) { result.path("/"); } } } @Override public DefaultUriBuilder scheme(@Nullable String scheme) { this.uriComponentsBuilder.scheme(scheme); return this; } @Override public DefaultUriBuilder userInfo(@Nullable String userInfo) { this.uriComponentsBuilder.userInfo(userInfo); return this; } @Override public DefaultUriBuilder host(@Nullable String host) { this.uriComponentsBuilder.host(host); return this; } @Override public DefaultUriBuilder port(int port) { this.uriComponentsBuilder.port(port); return this; } @Override public DefaultUriBuilder port(@Nullable String port) { this.uriComponentsBuilder.port(port); return this; } @Override public DefaultUriBuilder path(String path) { this.uriComponentsBuilder.path(path); return this; } @Override public DefaultUriBuilder replacePath(@Nullable String path) { this.uriComponentsBuilder.replacePath(path); return this; } @Override public DefaultUriBuilder pathSegment(String... pathSegments) { this.uriComponentsBuilder.pathSegment(pathSegments); return this; } @Override public DefaultUriBuilder query(String query) { this.uriComponentsBuilder.query(query); return this; } @Override public DefaultUriBuilder replaceQuery(@Nullable String query) { this.uriComponentsBuilder.replaceQuery(query); return this; } @Override public DefaultUriBuilder queryParam(String name, Object... values) { this.uriComponentsBuilder.queryParam(name, values); return this; } @Override public DefaultUriBuilder replaceQueryParam(String name, Object... values) { this.uriComponentsBuilder.replaceQueryParam(name, values); return this; } @Override public DefaultUriBuilder queryParams(MultiValueMap<String, String> params) { this.uriComponentsBuilder.queryParams(params); return this; } @Override public DefaultUriBuilder replaceQueryParams(MultiValueMap<String, String> params) { this.uriComponentsBuilder.replaceQueryParams(params); return this; } @Override public DefaultUriBuilder fragment(@Nullable String fragment) { this.uriComponentsBuilder.fragment(fragment); return this; } @Override public URI build(Map<String, ?> uriVars) { if (!defaultUriVariables.isEmpty()) { Map<String, Object> map = new HashMap<>(); map.putAll(defaultUriVariables); map.putAll(uriVars); uriVars = map; } if (encodingMode.equals(EncodingMode.VALUES_ONLY)) { uriVars = UriUtils.encodeUriVariables(uriVars); } UriComponents uric = this.uriComponentsBuilder.build().expand(uriVars); return createUri(uric); } @Override public URI build(Object... uriVars) { if (ObjectUtils.isEmpty(uriVars) && !defaultUriVariables.isEmpty()) { return build(Collections.emptyMap()); } if (encodingMode.equals(EncodingMode.VALUES_ONLY)) { uriVars = UriUtils.encodeUriVariables(uriVars); } UriComponents uric = this.uriComponentsBuilder.build().expand(uriVars); return createUri(uric); } private URI createUri(UriComponents uric) { if (encodingMode.equals(EncodingMode.URI_COMPONENT)) { uric = uric.encode(); } return URI.create(uric.toString()); } } }