Merge pull request #148 from poutsma/SPR-9798
* poutsma-SPR-9798: Support opaque URIs in UriComponentsBuilder
This commit is contained in:
commit
8a082e6e3b
|
|
@ -0,0 +1,858 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Extension of {@link UriComponents} for hierarchical URIs.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.2
|
||||
* @see <a href="http://tools.ietf.org/html/rfc3986#section-1.2.3">Hierarchical URIs</a>
|
||||
*/
|
||||
final class HierarchicalUriComponents extends UriComponents {
|
||||
|
||||
private static final char PATH_DELIMITER = '/';
|
||||
|
||||
private final String userInfo;
|
||||
|
||||
private final String host;
|
||||
|
||||
private final int port;
|
||||
|
||||
private final PathComponent path;
|
||||
|
||||
private final MultiValueMap<String, String> queryParams;
|
||||
|
||||
private final boolean encoded;
|
||||
|
||||
/**
|
||||
* Package-private constructor. All arguments are optional, and can be {@code null}.
|
||||
*
|
||||
* @param scheme the scheme
|
||||
* @param userInfo the user info
|
||||
* @param host the host
|
||||
* @param port the port
|
||||
* @param path the path
|
||||
* @param queryParams the query parameters
|
||||
* @param fragment the fragment
|
||||
* @param encoded whether the components are already encoded
|
||||
* @param verify whether the components need to be checked for illegal characters
|
||||
*/
|
||||
HierarchicalUriComponents(String scheme, String userInfo, String host, int port,
|
||||
PathComponent path, MultiValueMap<String, String> queryParams,
|
||||
String fragment, boolean encoded, boolean verify) {
|
||||
|
||||
super(scheme, fragment);
|
||||
this.userInfo = userInfo;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.path = path != null ? path : NULL_PATH_COMPONENT;
|
||||
this.queryParams = CollectionUtils.unmodifiableMultiValueMap(
|
||||
queryParams != null ? queryParams : new LinkedMultiValueMap<String, String>(0));
|
||||
this.encoded = encoded;
|
||||
if (verify) {
|
||||
verify();
|
||||
}
|
||||
}
|
||||
|
||||
// component getters
|
||||
|
||||
@Override
|
||||
public String getSchemeSpecificPart() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserInfo() {
|
||||
return this.userInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost() {
|
||||
return this.host;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return this.path.getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPathSegments() {
|
||||
return this.path.getPathSegments();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuery() {
|
||||
if (!this.queryParams.isEmpty()) {
|
||||
StringBuilder queryBuilder = new StringBuilder();
|
||||
for (Map.Entry<String, List<String>> entry : this.queryParams.entrySet()) {
|
||||
String name = entry.getKey();
|
||||
List<String> values = entry.getValue();
|
||||
if (CollectionUtils.isEmpty(values)) {
|
||||
if (queryBuilder.length() != 0) {
|
||||
queryBuilder.append('&');
|
||||
}
|
||||
queryBuilder.append(name);
|
||||
}
|
||||
else {
|
||||
for (Object value : values) {
|
||||
if (queryBuilder.length() != 0) {
|
||||
queryBuilder.append('&');
|
||||
}
|
||||
queryBuilder.append(name);
|
||||
|
||||
if (value != null) {
|
||||
queryBuilder.append('=');
|
||||
queryBuilder.append(value.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return queryBuilder.toString();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the map of query parameters.
|
||||
*
|
||||
* @return the query parameters. Empty if no query has been set.
|
||||
*/
|
||||
@Override
|
||||
public MultiValueMap<String, String> getQueryParams() {
|
||||
return this.queryParams;
|
||||
}
|
||||
|
||||
// encoding
|
||||
|
||||
/**
|
||||
* Encodes all URI components using their specific encoding rules, and returns the result as a new
|
||||
* {@code UriComponents} instance.
|
||||
*
|
||||
* @param encoding the encoding of the values contained in this map
|
||||
* @return the encoded uri components
|
||||
* @throws UnsupportedEncodingException if the given encoding is not supported
|
||||
*/
|
||||
@Override
|
||||
public HierarchicalUriComponents encode(String encoding) throws UnsupportedEncodingException {
|
||||
Assert.hasLength(encoding, "'encoding' must not be empty");
|
||||
|
||||
if (this.encoded) {
|
||||
return this;
|
||||
}
|
||||
|
||||
String encodedScheme = encodeUriComponent(this.getScheme(), encoding, Type.SCHEME);
|
||||
String encodedUserInfo = encodeUriComponent(this.userInfo, encoding, Type.USER_INFO);
|
||||
String encodedHost = encodeUriComponent(this.host, encoding, Type.HOST);
|
||||
PathComponent encodedPath = this.path.encode(encoding);
|
||||
MultiValueMap<String, String> encodedQueryParams =
|
||||
new LinkedMultiValueMap<String, String>(this.queryParams.size());
|
||||
for (Map.Entry<String, List<String>> entry : this.queryParams.entrySet()) {
|
||||
String encodedName = encodeUriComponent(entry.getKey(), encoding, Type.QUERY_PARAM);
|
||||
List<String> encodedValues = new ArrayList<String>(entry.getValue().size());
|
||||
for (String value : entry.getValue()) {
|
||||
String encodedValue = encodeUriComponent(value, encoding, Type.QUERY_PARAM);
|
||||
encodedValues.add(encodedValue);
|
||||
}
|
||||
encodedQueryParams.put(encodedName, encodedValues);
|
||||
}
|
||||
String encodedFragment = encodeUriComponent(this.getFragment(), encoding, Type.FRAGMENT);
|
||||
|
||||
return new HierarchicalUriComponents(encodedScheme, encodedUserInfo, encodedHost, this.port, encodedPath,
|
||||
encodedQueryParams, encodedFragment, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given source into an encoded String using the rules specified
|
||||
* by the given component and with the given options.
|
||||
*
|
||||
* @param source the source string
|
||||
* @param encoding the encoding of the source string
|
||||
* @param type the URI component for the source
|
||||
* @return the encoded URI
|
||||
* @throws IllegalArgumentException when the given uri parameter is not a
|
||||
* valid URI
|
||||
*/
|
||||
static String encodeUriComponent(String source, String encoding, Type type)
|
||||
throws UnsupportedEncodingException {
|
||||
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Assert.hasLength(encoding, "'encoding' must not be empty");
|
||||
|
||||
byte[] bytes = encodeBytes(source.getBytes(encoding), type);
|
||||
return new String(bytes, "US-ASCII");
|
||||
}
|
||||
|
||||
private static byte[] encodeBytes(byte[] source, Type type) {
|
||||
Assert.notNull(source, "'source' must not be null");
|
||||
Assert.notNull(type, "'type' must not be null");
|
||||
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length);
|
||||
for (int i = 0; i < source.length; i++) {
|
||||
int b = source[i];
|
||||
if (b < 0) {
|
||||
b += 256;
|
||||
}
|
||||
if (type.isAllowed(b)) {
|
||||
bos.write(b);
|
||||
}
|
||||
else {
|
||||
bos.write('%');
|
||||
|
||||
char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
|
||||
char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
|
||||
|
||||
bos.write(hex1);
|
||||
bos.write(hex2);
|
||||
}
|
||||
}
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
// verifying
|
||||
|
||||
/**
|
||||
* Verifies all URI components to determine whether they contain any illegal
|
||||
* characters, throwing an {@code IllegalArgumentException} if so.
|
||||
*
|
||||
* @throws IllegalArgumentException if any component has illegal characters
|
||||
*/
|
||||
private void verify() {
|
||||
if (!this.encoded) {
|
||||
return;
|
||||
}
|
||||
verifyUriComponent(getScheme(), Type.SCHEME);
|
||||
verifyUriComponent(userInfo, Type.USER_INFO);
|
||||
verifyUriComponent(host, Type.HOST);
|
||||
this.path.verify();
|
||||
for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
|
||||
verifyUriComponent(entry.getKey(), Type.QUERY_PARAM);
|
||||
for (String value : entry.getValue()) {
|
||||
verifyUriComponent(value, Type.QUERY_PARAM);
|
||||
}
|
||||
}
|
||||
verifyUriComponent(getFragment(), Type.FRAGMENT);
|
||||
}
|
||||
|
||||
|
||||
private static void verifyUriComponent(String source, Type type) {
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int length = source.length();
|
||||
|
||||
for (int i=0; i < length; i++) {
|
||||
char ch = source.charAt(i);
|
||||
if (ch == '%') {
|
||||
if ((i + 2) < length) {
|
||||
char hex1 = source.charAt(i + 1);
|
||||
char hex2 = source.charAt(i + 2);
|
||||
int u = Character.digit(hex1, 16);
|
||||
int l = Character.digit(hex2, 16);
|
||||
if (u == -1 || l == -1) {
|
||||
throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\"");
|
||||
}
|
||||
}
|
||||
else if (!type.isAllowed(ch)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid character '" + ch + "' for " + type.name() + " in \"" + source + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// expanding
|
||||
|
||||
@Override
|
||||
protected HierarchicalUriComponents expandInternal(UriTemplateVariables uriVariables) {
|
||||
Assert.state(!encoded, "Cannot expand an already encoded UriComponents object");
|
||||
|
||||
String expandedScheme = expandUriComponent(this.getScheme(), uriVariables);
|
||||
String expandedUserInfo = expandUriComponent(this.userInfo, uriVariables);
|
||||
String expandedHost = expandUriComponent(this.host, uriVariables);
|
||||
PathComponent expandedPath = this.path.expand(uriVariables);
|
||||
MultiValueMap<String, String> expandedQueryParams =
|
||||
new LinkedMultiValueMap<String, String>(this.queryParams.size());
|
||||
for (Map.Entry<String, List<String>> entry : this.queryParams.entrySet()) {
|
||||
String expandedName = expandUriComponent(entry.getKey(), uriVariables);
|
||||
List<String> expandedValues = new ArrayList<String>(entry.getValue().size());
|
||||
for (String value : entry.getValue()) {
|
||||
String expandedValue = expandUriComponent(value, uriVariables);
|
||||
expandedValues.add(expandedValue);
|
||||
}
|
||||
expandedQueryParams.put(expandedName, expandedValues);
|
||||
}
|
||||
String expandedFragment = expandUriComponent(this.getFragment(), uriVariables);
|
||||
|
||||
return new HierarchicalUriComponents(expandedScheme, expandedUserInfo, expandedHost, this.port, expandedPath,
|
||||
expandedQueryParams, expandedFragment, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the path removing sequences like "path/..".
|
||||
* @see StringUtils#cleanPath(String)
|
||||
*/
|
||||
@Override
|
||||
public UriComponents normalize() {
|
||||
String normalizedPath = StringUtils.cleanPath(getPath());
|
||||
return new HierarchicalUriComponents(getScheme(), this.userInfo, this.host,
|
||||
this.port, new FullPathComponent(normalizedPath), this.queryParams,
|
||||
getFragment(), this.encoded, false);
|
||||
}
|
||||
|
||||
// other functionality
|
||||
|
||||
/**
|
||||
* Returns a URI string from this {@code UriComponents} instance.
|
||||
*
|
||||
* @return the URI string
|
||||
*/
|
||||
@Override
|
||||
public String toUriString() {
|
||||
StringBuilder uriBuilder = new StringBuilder();
|
||||
|
||||
if (getScheme() != null) {
|
||||
uriBuilder.append(getScheme());
|
||||
uriBuilder.append(':');
|
||||
}
|
||||
|
||||
if (this.userInfo != null || this.host != null) {
|
||||
uriBuilder.append("//");
|
||||
if (this.userInfo != null) {
|
||||
uriBuilder.append(this.userInfo);
|
||||
uriBuilder.append('@');
|
||||
}
|
||||
if (this.host != null) {
|
||||
uriBuilder.append(host);
|
||||
}
|
||||
if (this.port != -1) {
|
||||
uriBuilder.append(':');
|
||||
uriBuilder.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
String path = getPath();
|
||||
if (StringUtils.hasLength(path)) {
|
||||
if (uriBuilder.length() != 0 && path.charAt(0) != PATH_DELIMITER) {
|
||||
uriBuilder.append(PATH_DELIMITER);
|
||||
}
|
||||
uriBuilder.append(path);
|
||||
}
|
||||
|
||||
String query = getQuery();
|
||||
if (query != null) {
|
||||
uriBuilder.append('?');
|
||||
uriBuilder.append(query);
|
||||
}
|
||||
|
||||
if (getFragment() != null) {
|
||||
uriBuilder.append('#');
|
||||
uriBuilder.append(getFragment());
|
||||
}
|
||||
|
||||
return uriBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@code URI} from this {@code UriComponents} instance.
|
||||
*
|
||||
* @return the URI
|
||||
*/
|
||||
@Override
|
||||
public URI toUri() {
|
||||
try {
|
||||
if (this.encoded) {
|
||||
return new URI(toString());
|
||||
}
|
||||
else {
|
||||
String path = getPath();
|
||||
if (StringUtils.hasLength(path) && path.charAt(0) != PATH_DELIMITER) {
|
||||
path = PATH_DELIMITER + path;
|
||||
}
|
||||
return new URI(getScheme(), getUserInfo(), getHost(), getPort(), path, getQuery(),
|
||||
getFragment());
|
||||
}
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof OpaqueUriComponents)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HierarchicalUriComponents other = (HierarchicalUriComponents) o;
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(getScheme(), other.getScheme())) {
|
||||
return false;
|
||||
}
|
||||
if (ObjectUtils.nullSafeEquals(getUserInfo(), other.getUserInfo())) {
|
||||
return false;
|
||||
}
|
||||
if (ObjectUtils.nullSafeEquals(getHost(), other.getHost())) {
|
||||
return false;
|
||||
}
|
||||
if (this.port != other.port) {
|
||||
return false;
|
||||
}
|
||||
if (!this.path.equals(other.path)) {
|
||||
return false;
|
||||
}
|
||||
if (!this.queryParams.equals(other.queryParams)) {
|
||||
return false;
|
||||
}
|
||||
if (ObjectUtils.nullSafeEquals(getFragment(), other.getFragment())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ObjectUtils.nullSafeHashCode(getScheme());
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(this.userInfo);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(this.host);
|
||||
result = 31 * result + this.port;
|
||||
result = 31 * result + this.path.hashCode();
|
||||
result = 31 * result + this.queryParams.hashCode();
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(getFragment());
|
||||
return result;
|
||||
}
|
||||
|
||||
// inner types
|
||||
|
||||
/**
|
||||
* Enumeration used to identify the parts of a URI.
|
||||
* <p/>
|
||||
* Contains methods to indicate whether a given character is valid in a specific URI component.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>
|
||||
*/
|
||||
static enum Type {
|
||||
|
||||
SCHEME {
|
||||
@Override
|
||||
public boolean isAllowed(int c) {
|
||||
return isAlpha(c) || isDigit(c) || '+' == c || '-' == c || '.' == c;
|
||||
}
|
||||
},
|
||||
AUTHORITY {
|
||||
@Override
|
||||
public boolean isAllowed(int c) {
|
||||
return isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c;
|
||||
}
|
||||
},
|
||||
USER_INFO {
|
||||
@Override
|
||||
public boolean isAllowed(int c) {
|
||||
return isUnreserved(c) || isSubDelimiter(c) || ':' == c;
|
||||
}
|
||||
},
|
||||
HOST {
|
||||
@Override
|
||||
public boolean isAllowed(int c) {
|
||||
return isUnreserved(c) || isSubDelimiter(c);
|
||||
}
|
||||
},
|
||||
PORT {
|
||||
@Override
|
||||
public boolean isAllowed(int c) {
|
||||
return isDigit(c);
|
||||
}
|
||||
},
|
||||
PATH {
|
||||
@Override
|
||||
public boolean isAllowed(int c) {
|
||||
return isPchar(c) || '/' == c;
|
||||
}
|
||||
},
|
||||
PATH_SEGMENT {
|
||||
@Override
|
||||
public boolean isAllowed(int c) {
|
||||
return isPchar(c);
|
||||
}
|
||||
},
|
||||
QUERY {
|
||||
@Override
|
||||
public boolean isAllowed(int c) {
|
||||
return isPchar(c) || '/' == c || '?' == c;
|
||||
}
|
||||
},
|
||||
QUERY_PARAM {
|
||||
@Override
|
||||
public boolean isAllowed(int c) {
|
||||
if ('=' == c || '+' == c || '&' == c) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return isPchar(c) || '/' == c || '?' == c;
|
||||
}
|
||||
}
|
||||
},
|
||||
FRAGMENT {
|
||||
@Override
|
||||
public boolean isAllowed(int c) {
|
||||
return isPchar(c) || '/' == c || '?' == c;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates whether the given character is allowed in this URI component.
|
||||
*
|
||||
* @param c the character
|
||||
* @return {@code true} if the character is allowed; {@code false} otherwise
|
||||
*/
|
||||
public abstract boolean isAllowed(int c);
|
||||
|
||||
/**
|
||||
* Indicates whether the given character is in the {@code ALPHA} set.
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
|
||||
*/
|
||||
protected boolean isAlpha(int c) {
|
||||
return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given character is in the {@code DIGIT} set.
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
|
||||
*/
|
||||
protected boolean isDigit(int c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given character is in the {@code gen-delims} set.
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
|
||||
*/
|
||||
protected boolean isGenericDelimiter(int c) {
|
||||
return ':' == c || '/' == c || '?' == c || '#' == c || '[' == c || ']' == c || '@' == c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given character is in the {@code sub-delims} set.
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
|
||||
*/
|
||||
protected boolean isSubDelimiter(int c) {
|
||||
return '!' == c || '$' == c || '&' == c || '\'' == c || '(' == c || ')' == c || '*' == c || '+' == c ||
|
||||
',' == c || ';' == c || '=' == c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given character is in the {@code reserved} set.
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
|
||||
*/
|
||||
protected boolean isReserved(char c) {
|
||||
return isGenericDelimiter(c) || isReserved(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given character is in the {@code unreserved} set.
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
|
||||
*/
|
||||
protected boolean isUnreserved(int c) {
|
||||
return isAlpha(c) || isDigit(c) || '-' == c || '.' == c || '_' == c || '~' == c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the given character is in the {@code pchar} set.
|
||||
*
|
||||
* @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
|
||||
*/
|
||||
protected boolean isPchar(int c) {
|
||||
return isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the contract for path (segments).
|
||||
*/
|
||||
interface PathComponent {
|
||||
|
||||
String getPath();
|
||||
|
||||
List<String> getPathSegments();
|
||||
|
||||
PathComponent encode(String encoding) throws UnsupportedEncodingException;
|
||||
|
||||
void verify();
|
||||
|
||||
PathComponent expand(UriTemplateVariables uriVariables);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a path backed by a string.
|
||||
*/
|
||||
final static class FullPathComponent implements PathComponent {
|
||||
|
||||
private final String path;
|
||||
|
||||
FullPathComponent(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public List<String> getPathSegments() {
|
||||
String delimiter = new String(new char[]{PATH_DELIMITER});
|
||||
String[] pathSegments = StringUtils.tokenizeToStringArray(path, delimiter);
|
||||
return Collections.unmodifiableList(Arrays.asList(pathSegments));
|
||||
}
|
||||
|
||||
public PathComponent encode(String encoding) throws UnsupportedEncodingException {
|
||||
String encodedPath = encodeUriComponent(getPath(),encoding, Type.PATH);
|
||||
return new FullPathComponent(encodedPath);
|
||||
}
|
||||
|
||||
public void verify() {
|
||||
verifyUriComponent(this.path, Type.PATH);
|
||||
}
|
||||
|
||||
public PathComponent expand(UriTemplateVariables uriVariables) {
|
||||
String expandedPath = expandUriComponent(getPath(), uriVariables);
|
||||
return new FullPathComponent(expandedPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
} else if (o instanceof FullPathComponent) {
|
||||
FullPathComponent other = (FullPathComponent) o;
|
||||
return this.getPath().equals(other.getPath());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getPath().hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a path backed by a string list (i.e. path segments).
|
||||
*/
|
||||
final static class PathSegmentComponent implements PathComponent {
|
||||
|
||||
private final List<String> pathSegments;
|
||||
|
||||
PathSegmentComponent(List<String> pathSegments) {
|
||||
this.pathSegments = Collections.unmodifiableList(pathSegments);
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
StringBuilder pathBuilder = new StringBuilder();
|
||||
pathBuilder.append(PATH_DELIMITER);
|
||||
for (Iterator<String> iterator = this.pathSegments.iterator(); iterator.hasNext(); ) {
|
||||
String pathSegment = iterator.next();
|
||||
pathBuilder.append(pathSegment);
|
||||
if (iterator.hasNext()) {
|
||||
pathBuilder.append(PATH_DELIMITER);
|
||||
}
|
||||
}
|
||||
return pathBuilder.toString();
|
||||
}
|
||||
|
||||
public List<String> getPathSegments() {
|
||||
return this.pathSegments;
|
||||
}
|
||||
|
||||
public PathComponent encode(String encoding) throws UnsupportedEncodingException {
|
||||
List<String> pathSegments = getPathSegments();
|
||||
List<String> encodedPathSegments = new ArrayList<String>(pathSegments.size());
|
||||
for (String pathSegment : pathSegments) {
|
||||
String encodedPathSegment = encodeUriComponent(pathSegment, encoding, Type.PATH_SEGMENT);
|
||||
encodedPathSegments.add(encodedPathSegment);
|
||||
}
|
||||
return new PathSegmentComponent(encodedPathSegments);
|
||||
}
|
||||
|
||||
public void verify() {
|
||||
for (String pathSegment : getPathSegments()) {
|
||||
verifyUriComponent(pathSegment, Type.PATH_SEGMENT);
|
||||
}
|
||||
}
|
||||
|
||||
public PathComponent expand(UriTemplateVariables uriVariables) {
|
||||
List<String> pathSegments = getPathSegments();
|
||||
List<String> expandedPathSegments = new ArrayList<String>(pathSegments.size());
|
||||
for (String pathSegment : pathSegments) {
|
||||
String expandedPathSegment = expandUriComponent(pathSegment, uriVariables);
|
||||
expandedPathSegments.add(expandedPathSegment);
|
||||
}
|
||||
return new PathSegmentComponent(expandedPathSegments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
} else if (o instanceof PathSegmentComponent) {
|
||||
PathSegmentComponent other = (PathSegmentComponent) o;
|
||||
return this.getPathSegments().equals(other.getPathSegments());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getPathSegments().hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a collection of PathComponents.
|
||||
*/
|
||||
final static class PathComponentComposite implements PathComponent {
|
||||
|
||||
private final List<PathComponent> pathComponents;
|
||||
|
||||
PathComponentComposite(List<PathComponent> pathComponents) {
|
||||
this.pathComponents = pathComponents;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
StringBuilder pathBuilder = new StringBuilder();
|
||||
for (PathComponent pathComponent : this.pathComponents) {
|
||||
pathBuilder.append(pathComponent.getPath());
|
||||
}
|
||||
return pathBuilder.toString();
|
||||
}
|
||||
|
||||
public List<String> getPathSegments() {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (PathComponent pathComponent : this.pathComponents) {
|
||||
result.addAll(pathComponent.getPathSegments());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public PathComponent encode(String encoding) throws UnsupportedEncodingException {
|
||||
List<PathComponent> encodedComponents = new ArrayList<PathComponent>(pathComponents.size());
|
||||
for (PathComponent pathComponent : pathComponents) {
|
||||
encodedComponents.add(pathComponent.encode(encoding));
|
||||
}
|
||||
return new PathComponentComposite(encodedComponents);
|
||||
}
|
||||
|
||||
public void verify() {
|
||||
for (PathComponent pathComponent : pathComponents) {
|
||||
pathComponent.verify();
|
||||
}
|
||||
}
|
||||
|
||||
public PathComponent expand(UriTemplateVariables uriVariables) {
|
||||
List<PathComponent> expandedComponents = new ArrayList<PathComponent>(this.pathComponents.size());
|
||||
for (PathComponent pathComponent : this.pathComponents) {
|
||||
expandedComponents.add(pathComponent.expand(uriVariables));
|
||||
}
|
||||
return new PathComponentComposite(expandedComponents);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Represents an empty path.
|
||||
*/
|
||||
final static PathComponent NULL_PATH_COMPONENT = new PathComponent() {
|
||||
|
||||
public String getPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<String> getPathSegments() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public PathComponent encode(String encoding) throws UnsupportedEncodingException {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void verify() {
|
||||
}
|
||||
|
||||
public PathComponent expand(UriTemplateVariables uriVariables) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Copyright 2002-2012 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.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Extension of {@link UriComponents} for opaque URIs.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.2
|
||||
* @see <a href="http://tools.ietf.org/html/rfc3986#section-1.2.3">Hierarchical vs Opaque URIs</a>
|
||||
*/
|
||||
final class OpaqueUriComponents extends UriComponents {
|
||||
|
||||
private static final MultiValueMap<String, String> QUERY_PARAMS_NONE = new LinkedMultiValueMap<String, String>(0);
|
||||
|
||||
private final String ssp;
|
||||
|
||||
|
||||
OpaqueUriComponents(String scheme, String schemeSpecificPart, String fragment) {
|
||||
super(scheme, fragment);
|
||||
this.ssp = schemeSpecificPart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSchemeSpecificPart() {
|
||||
return this.ssp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserInfo() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHost() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getPathSegments() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQuery() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiValueMap<String, String> getQueryParams() {
|
||||
return QUERY_PARAMS_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriComponents encode(String encoding) throws UnsupportedEncodingException {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UriComponents expandInternal(UriTemplateVariables uriVariables) {
|
||||
String expandedScheme = expandUriComponent(this.getScheme(), uriVariables);
|
||||
String expandedSSp = expandUriComponent(this.ssp, uriVariables);
|
||||
String expandedFragment = expandUriComponent(this.getFragment(), uriVariables);
|
||||
|
||||
return new OpaqueUriComponents(expandedScheme, expandedSSp, expandedFragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toUriString() {
|
||||
StringBuilder uriBuilder = new StringBuilder();
|
||||
|
||||
if (getScheme() != null) {
|
||||
uriBuilder.append(getScheme());
|
||||
uriBuilder.append(':');
|
||||
}
|
||||
if (this.ssp != null) {
|
||||
uriBuilder.append(this.ssp);
|
||||
}
|
||||
if (getFragment() != null) {
|
||||
uriBuilder.append('#');
|
||||
uriBuilder.append(getFragment());
|
||||
}
|
||||
|
||||
return uriBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI toUri() {
|
||||
try {
|
||||
return new URI(getScheme(), this.ssp, getFragment());
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UriComponents normalize() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof OpaqueUriComponents)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OpaqueUriComponents other = (OpaqueUriComponents) o;
|
||||
|
||||
if (ObjectUtils.nullSafeEquals(getScheme(), other.getScheme())) {
|
||||
return false;
|
||||
}
|
||||
if (ObjectUtils.nullSafeEquals(this.ssp, other.ssp)) {
|
||||
return false;
|
||||
}
|
||||
if (ObjectUtils.nullSafeEquals(getFragment(), other.getFragment())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ObjectUtils.nullSafeHashCode(getScheme());
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(this.ssp);
|
||||
result = 31 * result + ObjectUtils.nullSafeHashCode(getFragment());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -83,6 +83,8 @@ public class UriComponentsBuilder {
|
|||
|
||||
private String scheme;
|
||||
|
||||
private String ssp;
|
||||
|
||||
private String userInfo;
|
||||
|
||||
private String host;
|
||||
|
|
@ -164,16 +166,43 @@ public class UriComponentsBuilder {
|
|||
if (m.matches()) {
|
||||
UriComponentsBuilder builder = new UriComponentsBuilder();
|
||||
|
||||
builder.scheme(m.group(2));
|
||||
builder.userInfo(m.group(5));
|
||||
builder.host(m.group(6));
|
||||
String scheme = m.group(2);
|
||||
String userInfo = m.group(5);
|
||||
String host = m.group(6);
|
||||
String port = m.group(8);
|
||||
if (StringUtils.hasLength(port)) {
|
||||
builder.port(Integer.parseInt(port));
|
||||
String path = m.group(9);
|
||||
String query = m.group(11);
|
||||
String fragment = m.group(13);
|
||||
|
||||
boolean opaque = false;
|
||||
|
||||
if (StringUtils.hasLength(scheme)) {
|
||||
String s = uri.substring(scheme.length());
|
||||
if (!s.startsWith(":/")) {
|
||||
opaque = true;
|
||||
}
|
||||
}
|
||||
builder.path(m.group(9));
|
||||
builder.query(m.group(11));
|
||||
builder.fragment(m.group(13));
|
||||
|
||||
builder.scheme(scheme);
|
||||
|
||||
|
||||
if (opaque) {
|
||||
String ssp = uri.substring(scheme.length()).substring(1);
|
||||
if (StringUtils.hasLength(fragment)) {
|
||||
ssp = ssp.substring(0, ssp.length() - (fragment.length() + 1));
|
||||
}
|
||||
builder.schemeSpecificPart(ssp);
|
||||
}
|
||||
else {
|
||||
builder.userInfo(userInfo);
|
||||
builder.host(host);
|
||||
if (StringUtils.hasLength(port)) {
|
||||
builder.port(Integer.parseInt(port));
|
||||
}
|
||||
builder.path(path);
|
||||
builder.query(query);
|
||||
}
|
||||
builder.fragment(fragment);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
|
@ -244,7 +273,13 @@ public class UriComponentsBuilder {
|
|||
* @return the URI components
|
||||
*/
|
||||
public UriComponents build(boolean encoded) {
|
||||
return new UriComponents(scheme, userInfo, host, port, pathBuilder.build(), queryParams, fragment, encoded, true);
|
||||
if (ssp != null) {
|
||||
return new OpaqueUriComponents(scheme, ssp, fragment);
|
||||
}
|
||||
else {
|
||||
return new HierarchicalUriComponents(
|
||||
scheme, userInfo, host, port, pathBuilder.build(), queryParams, fragment, encoded, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -281,25 +316,31 @@ public class UriComponentsBuilder {
|
|||
*/
|
||||
public UriComponentsBuilder uri(URI uri) {
|
||||
Assert.notNull(uri, "'uri' must not be null");
|
||||
Assert.isTrue(!uri.isOpaque(), "Opaque URI [" + uri + "] not supported");
|
||||
|
||||
this.scheme = uri.getScheme();
|
||||
|
||||
if (uri.getRawUserInfo() != null) {
|
||||
this.userInfo = uri.getRawUserInfo();
|
||||
if (uri.isOpaque()) {
|
||||
this.ssp = uri.getRawSchemeSpecificPart();
|
||||
resetHierarchicalComponents();
|
||||
}
|
||||
if (uri.getHost() != null) {
|
||||
this.host = uri.getHost();
|
||||
}
|
||||
if (uri.getPort() != -1) {
|
||||
this.port = uri.getPort();
|
||||
}
|
||||
if (StringUtils.hasLength(uri.getRawPath())) {
|
||||
this.pathBuilder = new FullPathComponentBuilder(uri.getRawPath());
|
||||
}
|
||||
if (StringUtils.hasLength(uri.getRawQuery())) {
|
||||
this.queryParams.clear();
|
||||
query(uri.getRawQuery());
|
||||
else {
|
||||
if (uri.getRawUserInfo() != null) {
|
||||
this.userInfo = uri.getRawUserInfo();
|
||||
}
|
||||
if (uri.getHost() != null) {
|
||||
this.host = uri.getHost();
|
||||
}
|
||||
if (uri.getPort() != -1) {
|
||||
this.port = uri.getPort();
|
||||
}
|
||||
if (StringUtils.hasLength(uri.getRawPath())) {
|
||||
this.pathBuilder = new FullPathComponentBuilder(uri.getRawPath());
|
||||
}
|
||||
if (StringUtils.hasLength(uri.getRawQuery())) {
|
||||
this.queryParams.clear();
|
||||
query(uri.getRawQuery());
|
||||
}
|
||||
resetSchemeSpecificPart();
|
||||
}
|
||||
if (uri.getRawFragment() != null) {
|
||||
this.fragment = uri.getRawFragment();
|
||||
|
|
@ -307,6 +348,18 @@ public class UriComponentsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
private void resetHierarchicalComponents() {
|
||||
this.userInfo = null;
|
||||
this.host = null;
|
||||
this.port = -1;
|
||||
this.pathBuilder = NULL_PATH_COMPONENT_BUILDER;
|
||||
this.queryParams.clear();
|
||||
}
|
||||
|
||||
private void resetSchemeSpecificPart() {
|
||||
this.ssp = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URI scheme. The given scheme may contain URI template variables,
|
||||
* and may also be {@code null} to clear the scheme of this builder.
|
||||
|
|
@ -320,17 +373,32 @@ public class UriComponentsBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URI scheme-specific-part. When invoked, this method overwrites
|
||||
* {@linkplain #userInfo(String) user-info}, {@linkplain #host(String) host},
|
||||
* {@linkplain #port(int) port}, {@linkplain #path(String) path}, and
|
||||
* {@link #query(String) query}.
|
||||
*
|
||||
* @param ssp the URI scheme-specific-part, may contain URI template parameters
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder schemeSpecificPart(String ssp) {
|
||||
this.ssp = ssp;
|
||||
resetHierarchicalComponents();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URI user info. The given user info may contain URI template
|
||||
* variables, and may also be {@code null} to clear the user info of this
|
||||
* builder.
|
||||
*
|
||||
* @param userInfo
|
||||
* the URI user info
|
||||
* @param userInfo the URI user info
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder userInfo(String userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -338,12 +406,12 @@ public class UriComponentsBuilder {
|
|||
* Sets the URI host. The given host may contain URI template variables, and
|
||||
* may also be {@code null} to clear the host of this builder.
|
||||
*
|
||||
* @param host
|
||||
* the URI host
|
||||
* @param host the URI host
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder host(String host) {
|
||||
this.host = host;
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -356,6 +424,7 @@ public class UriComponentsBuilder {
|
|||
public UriComponentsBuilder port(int port) {
|
||||
Assert.isTrue(port >= -1, "'port' must not be < -1");
|
||||
this.port = port;
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -363,8 +432,7 @@ public class UriComponentsBuilder {
|
|||
* Appends the given path to the existing path of this builder. The given
|
||||
* path may contain URI template variables.
|
||||
*
|
||||
* @param path
|
||||
* the URI path
|
||||
* @param path the URI path
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder path(String path) {
|
||||
|
|
@ -374,6 +442,7 @@ public class UriComponentsBuilder {
|
|||
else {
|
||||
this.pathBuilder = NULL_PATH_COMPONENT_BUILDER;
|
||||
}
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -386,6 +455,7 @@ public class UriComponentsBuilder {
|
|||
public UriComponentsBuilder replacePath(String path) {
|
||||
this.pathBuilder = NULL_PATH_COMPONENT_BUILDER;
|
||||
path(path);
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -399,6 +469,7 @@ public class UriComponentsBuilder {
|
|||
public UriComponentsBuilder pathSegment(String... pathSegments) throws IllegalArgumentException {
|
||||
Assert.notNull(pathSegments, "'segments' must not be null");
|
||||
this.pathBuilder = this.pathBuilder.appendPathSegments(pathSegments);
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -432,6 +503,7 @@ public class UriComponentsBuilder {
|
|||
else {
|
||||
this.queryParams.clear();
|
||||
}
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -444,6 +516,7 @@ public class UriComponentsBuilder {
|
|||
public UriComponentsBuilder replaceQuery(String query) {
|
||||
this.queryParams.clear();
|
||||
query(query);
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -470,6 +543,7 @@ public class UriComponentsBuilder {
|
|||
else {
|
||||
this.queryParams.add(name, null);
|
||||
}
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -490,6 +564,7 @@ public class UriComponentsBuilder {
|
|||
if (!ObjectUtils.isEmpty(values)) {
|
||||
queryParam(name, values);
|
||||
}
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -514,11 +589,11 @@ public class UriComponentsBuilder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Represents a builder for {@link org.springframework.web.util.UriComponents.PathComponent}
|
||||
* Represents a builder for {@link HierarchicalUriComponents.PathComponent}
|
||||
*/
|
||||
private interface PathComponentBuilder {
|
||||
|
||||
UriComponents.PathComponent build();
|
||||
HierarchicalUriComponents.PathComponent build();
|
||||
|
||||
PathComponentBuilder appendPath(String path);
|
||||
|
||||
|
|
@ -536,8 +611,8 @@ public class UriComponentsBuilder {
|
|||
this.path = new StringBuilder(path);
|
||||
}
|
||||
|
||||
public UriComponents.PathComponent build() {
|
||||
return new UriComponents.FullPathComponent(path.toString());
|
||||
public HierarchicalUriComponents.PathComponent build() {
|
||||
return new HierarchicalUriComponents.FullPathComponent(path.toString());
|
||||
}
|
||||
|
||||
public PathComponentBuilder appendPath(String path) {
|
||||
|
|
@ -573,8 +648,8 @@ public class UriComponentsBuilder {
|
|||
return result;
|
||||
}
|
||||
|
||||
public UriComponents.PathComponent build() {
|
||||
return new UriComponents.PathSegmentComponent(pathSegments);
|
||||
public HierarchicalUriComponents.PathComponent build() {
|
||||
return new HierarchicalUriComponents.PathSegmentComponent(pathSegments);
|
||||
}
|
||||
|
||||
public PathComponentBuilder appendPath(String path) {
|
||||
|
|
@ -600,14 +675,14 @@ public class UriComponentsBuilder {
|
|||
pathComponentBuilders.add(builder);
|
||||
}
|
||||
|
||||
public UriComponents.PathComponent build() {
|
||||
List<UriComponents.PathComponent> pathComponents =
|
||||
new ArrayList<UriComponents.PathComponent>(pathComponentBuilders.size());
|
||||
public HierarchicalUriComponents.PathComponent build() {
|
||||
List<HierarchicalUriComponents.PathComponent> pathComponents =
|
||||
new ArrayList<HierarchicalUriComponents.PathComponent>(pathComponentBuilders.size());
|
||||
|
||||
for (PathComponentBuilder pathComponentBuilder : pathComponentBuilders) {
|
||||
pathComponents.add(pathComponentBuilder.build());
|
||||
}
|
||||
return new UriComponents.PathComponentComposite(pathComponents);
|
||||
return new HierarchicalUriComponents.PathComponentComposite(pathComponents);
|
||||
}
|
||||
|
||||
public PathComponentBuilder appendPath(String path) {
|
||||
|
|
@ -627,8 +702,8 @@ public class UriComponentsBuilder {
|
|||
*/
|
||||
private static PathComponentBuilder NULL_PATH_COMPONENT_BUILDER = new PathComponentBuilder() {
|
||||
|
||||
public UriComponents.PathComponent build() {
|
||||
return UriComponents.NULL_PATH_COMPONENT;
|
||||
public HierarchicalUriComponents.PathComponent build() {
|
||||
return HierarchicalUriComponents.NULL_PATH_COMPONENT;
|
||||
}
|
||||
|
||||
public PathComponentBuilder appendPath(String path) {
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
* @deprecated in favor of {@link UriComponentsBuilder}; see note about query param encoding
|
||||
*/
|
||||
@Deprecated
|
||||
public static String encodeUri(String uri, String encoding) throws UnsupportedEncodingException {
|
||||
Assert.notNull(uri, "'uri' must not be null");
|
||||
Assert.hasLength(encoding, "'encoding' must not be empty");
|
||||
|
|
@ -123,6 +124,7 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
* @deprecated in favor of {@link UriComponentsBuilder}; see note about query param encoding
|
||||
*/
|
||||
@Deprecated
|
||||
public static String encodeHttpUrl(String httpUrl, String encoding) throws UnsupportedEncodingException {
|
||||
Assert.notNull(httpUrl, "'httpUrl' must not be null");
|
||||
Assert.hasLength(encoding, "'encoding' must not be empty");
|
||||
|
|
@ -160,6 +162,7 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
* @deprecated in favor of {@link UriComponentsBuilder}
|
||||
*/
|
||||
@Deprecated
|
||||
public static String encodeUriComponents(String scheme, String authority, String userInfo,
|
||||
String host, String port, String path, String query, String fragment, String encoding)
|
||||
throws UnsupportedEncodingException {
|
||||
|
|
@ -213,7 +216,8 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeScheme(String scheme, String encoding) throws UnsupportedEncodingException {
|
||||
return UriComponents.encodeUriComponent(scheme, encoding, UriComponents.Type.SCHEME);
|
||||
return HierarchicalUriComponents.encodeUriComponent(scheme, encoding,
|
||||
HierarchicalUriComponents.Type.SCHEME);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -224,7 +228,8 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeAuthority(String authority, String encoding) throws UnsupportedEncodingException {
|
||||
return UriComponents.encodeUriComponent(authority, encoding, UriComponents.Type.AUTHORITY);
|
||||
return HierarchicalUriComponents.encodeUriComponent(authority, encoding,
|
||||
HierarchicalUriComponents.Type.AUTHORITY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -235,7 +240,8 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeUserInfo(String userInfo, String encoding) throws UnsupportedEncodingException {
|
||||
return UriComponents.encodeUriComponent(userInfo, encoding, UriComponents.Type.USER_INFO);
|
||||
return HierarchicalUriComponents.encodeUriComponent(userInfo, encoding,
|
||||
HierarchicalUriComponents.Type.USER_INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -246,7 +252,8 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeHost(String host, String encoding) throws UnsupportedEncodingException {
|
||||
return UriComponents.encodeUriComponent(host, encoding, UriComponents.Type.HOST);
|
||||
return HierarchicalUriComponents
|
||||
.encodeUriComponent(host, encoding, HierarchicalUriComponents.Type.HOST);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -257,7 +264,8 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodePort(String port, String encoding) throws UnsupportedEncodingException {
|
||||
return UriComponents.encodeUriComponent(port, encoding, UriComponents.Type.PORT);
|
||||
return HierarchicalUriComponents
|
||||
.encodeUriComponent(port, encoding, HierarchicalUriComponents.Type.PORT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -268,7 +276,8 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodePath(String path, String encoding) throws UnsupportedEncodingException {
|
||||
return UriComponents.encodeUriComponent(path, encoding, UriComponents.Type.PATH);
|
||||
return HierarchicalUriComponents
|
||||
.encodeUriComponent(path, encoding, HierarchicalUriComponents.Type.PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -279,7 +288,8 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodePathSegment(String segment, String encoding) throws UnsupportedEncodingException {
|
||||
return UriComponents.encodeUriComponent(segment, encoding, UriComponents.Type.PATH_SEGMENT);
|
||||
return HierarchicalUriComponents.encodeUriComponent(segment, encoding,
|
||||
HierarchicalUriComponents.Type.PATH_SEGMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -290,7 +300,8 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeQuery(String query, String encoding) throws UnsupportedEncodingException {
|
||||
return UriComponents.encodeUriComponent(query, encoding, UriComponents.Type.QUERY);
|
||||
return HierarchicalUriComponents
|
||||
.encodeUriComponent(query, encoding, HierarchicalUriComponents.Type.QUERY);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -301,7 +312,8 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeQueryParam(String queryParam, String encoding) throws UnsupportedEncodingException {
|
||||
return UriComponents.encodeUriComponent(queryParam, encoding, UriComponents.Type.QUERY_PARAM);
|
||||
return HierarchicalUriComponents.encodeUriComponent(queryParam, encoding,
|
||||
HierarchicalUriComponents.Type.QUERY_PARAM);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -312,7 +324,8 @@ public abstract class UriUtils {
|
|||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeFragment(String fragment, String encoding) throws UnsupportedEncodingException {
|
||||
return UriComponents.encodeUriComponent(fragment, encoding, UriComponents.Type.FRAGMENT);
|
||||
return HierarchicalUriComponents.encodeUriComponent(fragment, encoding,
|
||||
HierarchicalUriComponents.Type.FRAGMENT);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ public class UriComponentsBuilderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void fromUri() throws URISyntaxException {
|
||||
public void fromHierarchicalUri() throws URISyntaxException {
|
||||
URI uri = new URI("http://example.com/foo?bar#baz");
|
||||
UriComponents result = UriComponentsBuilder.fromUri(uri).build();
|
||||
assertEquals("http", result.getScheme());
|
||||
|
|
@ -76,6 +76,17 @@ public class UriComponentsBuilderTests {
|
|||
assertEquals("Invalid result URI", uri, result.toUri());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromOpaqueUri() throws URISyntaxException {
|
||||
URI uri = new URI("mailto:foo@bar.com#baz");
|
||||
UriComponents result = UriComponentsBuilder.fromUri(uri).build();
|
||||
assertEquals("mailto", result.getScheme());
|
||||
assertEquals("foo@bar.com", result.getSchemeSpecificPart());
|
||||
assertEquals("baz", result.getFragment());
|
||||
|
||||
assertEquals("Invalid result URI", uri, result.toUri());
|
||||
}
|
||||
|
||||
// SPR-9317
|
||||
|
||||
@Test
|
||||
|
|
@ -113,14 +124,15 @@ public class UriComponentsBuilderTests {
|
|||
assertEquals(expectedQueryParams, result.getQueryParams());
|
||||
assertEquals("and(java.util.BitSet)", result.getFragment());
|
||||
|
||||
result = UriComponentsBuilder.fromUriString("mailto:java-net@java.sun.com").build();
|
||||
result = UriComponentsBuilder.fromUriString("mailto:java-net@java.sun.com#baz").build();
|
||||
assertEquals("mailto", result.getScheme());
|
||||
assertNull(result.getUserInfo());
|
||||
assertNull(result.getHost());
|
||||
assertEquals(-1, result.getPort());
|
||||
assertEquals("java-net@java.sun.com", result.getPathSegments().get(0));
|
||||
assertEquals("java-net@java.sun.com", result.getSchemeSpecificPart());
|
||||
assertNull(result.getPath());
|
||||
assertNull(result.getQuery());
|
||||
assertNull(result.getFragment());
|
||||
assertEquals("baz", result.getFragment());
|
||||
|
||||
result = UriComponentsBuilder.fromUriString("docs/guide/collections/designfaq.html#28").build();
|
||||
assertNull(result.getScheme());
|
||||
|
|
@ -265,7 +277,7 @@ public class UriComponentsBuilderTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void buildAndExpand() {
|
||||
public void buildAndExpandHierarchical() {
|
||||
UriComponents result = UriComponentsBuilder.fromPath("/{foo}").buildAndExpand("fooValue");
|
||||
assertEquals("/fooValue", result.toUriString());
|
||||
|
||||
|
|
@ -275,4 +287,17 @@ public class UriComponentsBuilderTests {
|
|||
result = UriComponentsBuilder.fromPath("/{foo}/{bar}").buildAndExpand(values);
|
||||
assertEquals("/fooValue/barValue", result.toUriString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildAndExpandOpaque() {
|
||||
UriComponents result = UriComponentsBuilder.fromUriString("mailto:{user}@{domain}").buildAndExpand("foo", "example.com");
|
||||
assertEquals("mailto:foo@example.com", result.toUriString());
|
||||
|
||||
Map<String, String> values = new HashMap<String, String>();
|
||||
values.put("user", "foo");
|
||||
values.put("domain", "example.com");
|
||||
UriComponentsBuilder.fromUriString("mailto:{user}@{domain}").buildAndExpand(values);
|
||||
assertEquals("mailto:foo@example.com", result.toUriString());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue