org.springframework.web.util.DefaultUriBuilderFactory Maven / Gradle / Ivy
/*
* 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.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* {@code UriBuilderFactory} that relies on {@link UriComponentsBuilder} for
* the actual building of the URI.
*
* 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 {
/**
* Constants that represent different URI encoding strategies.
* @see #setEncodingMode
*/
public enum EncodingMode {
/**
* The default way of encoding that {@link UriComponents} supports:
*
* - Expand URI variables.
*
- Encode individual URI components as described in
* {@link UriComponents#encode(Charset)}.
*
* This mode does not encode all characters with
* reserved meaning but only the ones that are illegal within a given
* URI component as defined in RFC 396. This matches the way the
* multi-argument {@link URI} constructor does encoding.
*/
URI_COMPONENT,
/**
* Comprehensive encoding of URI variable values prior to expanding:
*
* - Apply {@link UriUtils#encode(String, Charset)} to each URI variable value.
*
- Expand URI variable values.
*
* This mode encodes all characters with reserved meaning, therefore
* ensuring that expanded URI variable do not have any impact on the
* structure or meaning of the URI.
*/
VALUES_ONLY,
/**
* No encoding should be applied.
*/
NONE }
private final UriComponentsBuilder baseUri;
private final Map defaultUriVariables = new HashMap<>();
private EncodingMode encodingMode = EncodingMode.URI_COMPONENT;
private boolean parsePath = true;
/**
* Default constructor without a base URI.
* The target address must be specified on each UriBuilder.
*/
public DefaultUriBuilderFactory() {
this(UriComponentsBuilder.newInstance());
}
/**
* Constructor with a base URI.
*
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(UriComponentsBuilder.fromUriString(baseUriTemplate));
}
/**
* Variant of {@link #DefaultUriBuilderFactory(String)} with a
* {@code UriComponentsBuilder}.
*/
public DefaultUriBuilderFactory(UriComponentsBuilder baseUri) {
Assert.notNull(baseUri, "'baseUri' is required");
this.baseUri = baseUri;
}
/**
* 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 defaultUriVariables) {
this.defaultUriVariables.clear();
if (defaultUriVariables != null) {
this.defaultUriVariables.putAll(defaultUriVariables);
}
}
/**
* Return the configured default URI variable values.
*/
public Map getDefaultUriVariables() {
return Collections.unmodifiableMap(this.defaultUriVariables);
}
/**
* Specify the {@link EncodingMode EncodingMode} to use when building URIs.
* By default set to
* {@link EncodingMode#URI_COMPONENT EncodingMode.URI_COMPONENT}.
* @param encodingMode the encoding mode to use
*/
public void setEncodingMode(EncodingMode encodingMode) {
this.encodingMode = encodingMode;
}
/**
* Return the configured encoding mode.
*/
public EncodingMode getEncodingMode() {
return this.encodingMode;
}
/**
* Whether to parse the path into path segments for the URI string passed
* into {@link #uriString(String)} or one of the expand methods.
*
Setting this property to {@code true} ensures that URI variables
* expanded into the path are subject to path segment encoding rules and
* "/" characters are percent-encoded. If set to {@code false} the path is
* kept as a full path and expanded URI variables will have "/" characters
* preserved.
*
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 the handler is configured to parse the path into path segments.
*/
public boolean shouldParsePath() {
return this.parsePath;
}
// UriTemplateHandler
public URI expand(String uriTemplate, Map 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("");
}
/**
* {@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 uriComponentsBuilder = UriComponentsBuilder.fromUriString(uriTemplate);
UriComponents uriComponents = uriComponentsBuilder.build();
UriComponentsBuilder result = (uriComponents.getHost() == null ?
baseUri.cloneBuilder().uriComponents(uriComponents) : uriComponentsBuilder);
if (shouldParsePath()) {
UriComponents uric = result.build();
String path = uric.getPath();
List pathSegments = uric.getPathSegments();
result.replacePath(null);
result.pathSegment(StringUtils.toStringArray(pathSegments));
if (path != null && path.endsWith("/")) {
result.path("/");
}
}
return result;
}
@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 params) {
this.uriComponentsBuilder.queryParams(params);
return this;
}
@Override
public DefaultUriBuilder replaceQueryParams(MultiValueMap 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 uriVars) {
if (!defaultUriVariables.isEmpty()) {
Map map = new HashMap<>();
map.putAll(defaultUriVariables);
map.putAll(uriVars);
uriVars = map;
}
if (encodingMode.equals(EncodingMode.VALUES_ONLY)) {
uriVars = UriUtils.encodeUriVariables(uriVars);
}
UriComponents uriComponents = this.uriComponentsBuilder.build().expand(uriVars);
if (encodingMode.equals(EncodingMode.URI_COMPONENT)) {
uriComponents = uriComponents.encode();
}
return URI.create(uriComponents.toString());
}
@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 uriComponents = this.uriComponentsBuilder.build().expand(uriVars);
if (encodingMode.equals(EncodingMode.URI_COMPONENT)) {
uriComponents = uriComponents.encode();
}
return URI.create(uriComponents.toString());
}
}
}