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}