Backported "Support opaque URIs in UriComponentsBuilder"
Issue: SPR-9798 Issue: SPR-9804
This commit is contained in:
@@ -0,0 +1,836 @@
|
||||
/*
|
||||
* 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. 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.
|
||||
*/
|
||||
@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.
|
||||
*/
|
||||
@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 obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof OpaqueUriComponents)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HierarchicalUriComponents other = (HierarchicalUriComponents) obj;
|
||||
|
||||
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.
|
||||
* @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 obj) {
|
||||
return (this == obj || (obj instanceof FullPathComponent &&
|
||||
getPath().equals(((FullPathComponent) obj).getPath())));
|
||||
}
|
||||
|
||||
@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 obj) {
|
||||
return (this == obj || (obj instanceof PathSegmentComponent &&
|
||||
getPathSegments().equals(((PathSegmentComponent) obj).getPathSegments())));
|
||||
}
|
||||
|
||||
@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 obj) {
|
||||
return (this == obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 42;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof OpaqueUriComponents)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OpaqueUriComponents other = (OpaqueUriComponents) obj;
|
||||
|
||||
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
@@ -32,8 +32,8 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Builder for {@link UriComponents}.
|
||||
* <p/>
|
||||
* Typical usage involves:
|
||||
*
|
||||
* <p></p>Typical usage involves:
|
||||
* <ol>
|
||||
* <li>Create a {@code UriComponentsBuilder} with one of the static factory methods (such as
|
||||
* {@link #fromPath(String)} or {@link #fromUri(URI)})</li>
|
||||
@@ -46,10 +46,10 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
* @see #newInstance()
|
||||
* @see #fromPath(String)
|
||||
* @see #fromUri(URI)
|
||||
* @since 3.1
|
||||
*/
|
||||
public class UriComponentsBuilder {
|
||||
|
||||
@@ -83,6 +83,8 @@ public class UriComponentsBuilder {
|
||||
|
||||
private String scheme;
|
||||
|
||||
private String ssp;
|
||||
|
||||
private String userInfo;
|
||||
|
||||
private String host;
|
||||
@@ -105,7 +107,7 @@ public class UriComponentsBuilder {
|
||||
protected UriComponentsBuilder() {
|
||||
}
|
||||
|
||||
// Factory methods
|
||||
// Factory methods
|
||||
|
||||
/**
|
||||
* Returns a new, empty builder.
|
||||
@@ -143,7 +145,19 @@ public class UriComponentsBuilder {
|
||||
/**
|
||||
* Returns a builder that is initialized with the given URI string.
|
||||
*
|
||||
* @param uri the URI string to initialize with
|
||||
* <p><strong>Note:</strong> The presence of reserved characters can prevent
|
||||
* correct parsing of the URI string. For example if a query parameter
|
||||
* contains {@code '='} or {@code '&'} characters, the query string cannot
|
||||
* be parsed unambiguously. Such values should be substituted for URI
|
||||
* variables to enable correct parsing:
|
||||
*
|
||||
* <pre>
|
||||
* String uriString = "/hotels/42?filter={value}";
|
||||
* UriComponentsBuilder.fromUriString(uriString).buildAndExpand("hot&cold");
|
||||
* </pre>
|
||||
*
|
||||
* @param uri
|
||||
* the URI string to initialize with
|
||||
* @return the new {@code UriComponentsBuilder}
|
||||
*/
|
||||
public static UriComponentsBuilder fromUriString(String uri) {
|
||||
@@ -152,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;
|
||||
}
|
||||
@@ -173,6 +214,17 @@ public class UriComponentsBuilder {
|
||||
/**
|
||||
* Creates a new {@code UriComponents} object from the string HTTP URL.
|
||||
*
|
||||
* <p><strong>Note:</strong> The presence of reserved characters can prevent
|
||||
* correct parsing of the URI string. For example if a query parameter
|
||||
* contains {@code '='} or {@code '&'} characters, the query string cannot
|
||||
* be parsed unambiguously. Such values should be substituted for URI
|
||||
* variables to enable correct parsing:
|
||||
*
|
||||
* <pre>
|
||||
* String uriString = "/hotels/42?filter={value}";
|
||||
* UriComponentsBuilder.fromUriString(uriString).buildAndExpand("hot&cold");
|
||||
* </pre>
|
||||
*
|
||||
* @param httpUrl the source URI
|
||||
* @return the URI components of the URI
|
||||
*/
|
||||
@@ -201,7 +253,7 @@ public class UriComponentsBuilder {
|
||||
|
||||
|
||||
|
||||
// build methods
|
||||
// build methods
|
||||
|
||||
/**
|
||||
* Builds a {@code UriComponents} instance from the various components contained in this builder.
|
||||
@@ -213,13 +265,21 @@ public class UriComponentsBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@code UriComponents} instance from the various components contained in this builder.
|
||||
* Builds a {@code UriComponents} instance from the various components
|
||||
* contained in this builder.
|
||||
*
|
||||
* @param encoded whether all the components set in this builder are encoded ({@code true}) or not ({@code false}).
|
||||
* @param encoded whether all the components set in this builder are
|
||||
* encoded ({@code true}) or not ({@code false}).
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -246,7 +306,7 @@ public class UriComponentsBuilder {
|
||||
return build(false).expand(uriVariableValues);
|
||||
}
|
||||
|
||||
// URI components methods
|
||||
// URI components methods
|
||||
|
||||
/**
|
||||
* Initializes all components of this URI builder with the components of the given URI.
|
||||
@@ -256,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();
|
||||
@@ -282,11 +348,24 @@ 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.
|
||||
* 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.
|
||||
*
|
||||
* @param scheme the URI scheme
|
||||
* @param scheme
|
||||
* the URI scheme
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder scheme(String scheme) {
|
||||
@@ -295,26 +374,44 @@ public class UriComponentsBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder userInfo(String userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder host(String host) {
|
||||
this.host = host;
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -327,11 +424,13 @@ public class UriComponentsBuilder {
|
||||
public UriComponentsBuilder port(int port) {
|
||||
Assert.isTrue(port >= -1, "'port' must not be < -1");
|
||||
this.port = port;
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given path to the existing path of this builder. The given path may contain URI template variables.
|
||||
* Appends the given path to the existing path of this builder. The given
|
||||
* path may contain URI template variables.
|
||||
*
|
||||
* @param path the URI path
|
||||
* @return this UriComponentsBuilder
|
||||
@@ -343,6 +442,7 @@ public class UriComponentsBuilder {
|
||||
else {
|
||||
this.pathBuilder = NULL_PATH_COMPONENT_BUILDER;
|
||||
}
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -355,6 +455,7 @@ public class UriComponentsBuilder {
|
||||
public UriComponentsBuilder replacePath(String path) {
|
||||
this.pathBuilder = NULL_PATH_COMPONENT_BUILDER;
|
||||
path(path);
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -368,11 +469,24 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given query to the existing query of this builder. The given query may contain URI template variables.
|
||||
* Appends the given query to the existing query of this builder.
|
||||
* The given query may contain URI template variables.
|
||||
*
|
||||
* <p><strong>Note:</strong> The presence of reserved characters can prevent
|
||||
* correct parsing of the URI string. For example if a query parameter
|
||||
* contains {@code '='} or {@code '&'} characters, the query string cannot
|
||||
* be parsed unambiguously. Such values should be substituted for URI
|
||||
* variables to enable correct parsing:
|
||||
*
|
||||
* <pre>
|
||||
* String uriString = "/hotels/42?filter={value}";
|
||||
* UriComponentsBuilder.fromUriString(uriString).buildAndExpand("hot&cold");
|
||||
* </pre>
|
||||
*
|
||||
* @param query the query string
|
||||
* @return this UriComponentsBuilder
|
||||
@@ -389,6 +503,7 @@ public class UriComponentsBuilder {
|
||||
else {
|
||||
this.queryParams.clear();
|
||||
}
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -401,16 +516,20 @@ public class UriComponentsBuilder {
|
||||
public UriComponentsBuilder replaceQuery(String query) {
|
||||
this.queryParams.clear();
|
||||
query(query);
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given query parameter to the existing query parameters. The given name or any of the values may contain
|
||||
* URI template variables. If no values are given, the resulting URI will contain the query parameter name only (i.e.
|
||||
* {@code ?foo} instead of {@code ?foo=bar}.
|
||||
* Appends the given query parameter to the existing query parameters. The
|
||||
* given name or any of the values may contain URI template variables. If no
|
||||
* values are given, the resulting URI will contain the query parameter name
|
||||
* only (i.e. {@code ?foo} instead of {@code ?foo=bar}.
|
||||
*
|
||||
* @param name the query parameter name
|
||||
* @param values the query parameter values
|
||||
* @param name
|
||||
* the query parameter name
|
||||
* @param values
|
||||
* the query parameter values
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder queryParam(String name, Object... values) {
|
||||
@@ -424,15 +543,19 @@ public class UriComponentsBuilder {
|
||||
else {
|
||||
this.queryParams.add(name, null);
|
||||
}
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the query parameter values overriding all existing query values for the same parameter.
|
||||
* If no values are given, the query parameter is removed.
|
||||
* Sets the query parameter values overriding all existing query values for
|
||||
* the same parameter. If no values are given, the query parameter is
|
||||
* removed.
|
||||
*
|
||||
* @param name the query parameter name
|
||||
* @param values the query parameter values
|
||||
* @param name
|
||||
* the query parameter name
|
||||
* @param values
|
||||
* the query parameter values
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder replaceQueryParam(String name, Object... values) {
|
||||
@@ -441,14 +564,17 @@ public class UriComponentsBuilder {
|
||||
if (!ObjectUtils.isEmpty(values)) {
|
||||
queryParam(name, values);
|
||||
}
|
||||
resetSchemeSpecificPart();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URI fragment. The given fragment may contain URI template variables, and may also be {@code null} to clear
|
||||
* the fragment of this builder.
|
||||
* Sets the URI fragment. The given fragment may contain URI template
|
||||
* variables, and may also be {@code null} to clear the fragment of this
|
||||
* builder.
|
||||
*
|
||||
* @param fragment the URI fragment
|
||||
* @param fragment
|
||||
* the URI fragment
|
||||
* @return this UriComponentsBuilder
|
||||
*/
|
||||
public UriComponentsBuilder fragment(String fragment) {
|
||||
@@ -463,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);
|
||||
|
||||
@@ -485,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) {
|
||||
@@ -522,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) {
|
||||
@@ -549,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) {
|
||||
@@ -576,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) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
* 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.
|
||||
@@ -29,9 +29,9 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* <p>All {@code encode*(String, String} methods in this class operate in a similar way:
|
||||
* <ul>
|
||||
* <li>Valid characters for the specific URI component as defined in RFC 3986 stay the same.</li>
|
||||
* <li>All other characters are converted into one or more bytes in the given encoding scheme. Each of the
|
||||
* resulting bytes is written as a hexadecimal string in the "<code>%<i>xy</i></code>" format.</li>
|
||||
* <li>Valid characters for the specific URI component as defined in RFC 3986 stay the same.</li>
|
||||
* <li>All other characters are converted into one or more bytes in the given encoding scheme. Each of the
|
||||
* resulting bytes is written as a hexadecimal string in the "<code>%<i>xy</i></code>" format.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
@@ -64,6 +64,7 @@ public abstract class UriUtils {
|
||||
private static final Pattern HTTP_URL_PATTERN = Pattern.compile(
|
||||
"^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" +
|
||||
PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
|
||||
|
||||
// encoding
|
||||
|
||||
/**
|
||||
@@ -73,8 +74,8 @@ public abstract class UriUtils {
|
||||
* characters in query parameter names and query parameter values because they cannot
|
||||
* be parsed in a reliable way. Instead use:
|
||||
* <pre>
|
||||
* UriComponents uriComponents = UriComponentsBuilder.fromUri("/path?name={value}").buildAndExpand("a=b");
|
||||
* String encodedUri = uriComponents.encode().toUriString();
|
||||
* UriComponents uriComponents = UriComponentsBuilder.fromUri("/path?name={value}").buildAndExpand("a=b");
|
||||
* String encodedUri = uriComponents.encode().toUriString();
|
||||
* </pre>
|
||||
* @param uri the URI to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
@@ -83,6 +84,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");
|
||||
@@ -113,8 +115,8 @@ public abstract class UriUtils {
|
||||
* characters in query parameter names and query parameter values because they cannot
|
||||
* be parsed in a reliable way. Instead use:
|
||||
* <pre>
|
||||
* UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl("/path?name={value}").buildAndExpand("a=b");
|
||||
* String encodedUri = uriComponents.encode().toUriString();
|
||||
* UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl("/path?name={value}").buildAndExpand("a=b");
|
||||
* String encodedUri = uriComponents.encode().toUriString();
|
||||
* </pre>
|
||||
* @param httpUrl the HTTP URL to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
@@ -123,6 +125,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,46 +163,47 @@ 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 {
|
||||
|
||||
Assert.hasLength(encoding, "'encoding' must not be empty");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Assert.hasLength(encoding, "'encoding' must not be empty");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
if (scheme != null) {
|
||||
sb.append(encodeScheme(scheme, encoding));
|
||||
sb.append(':');
|
||||
}
|
||||
if (scheme != null) {
|
||||
sb.append(encodeScheme(scheme, encoding));
|
||||
sb.append(':');
|
||||
}
|
||||
|
||||
if (authority != null) {
|
||||
sb.append("//");
|
||||
if (userInfo != null) {
|
||||
sb.append(encodeUserInfo(userInfo, encoding));
|
||||
sb.append('@');
|
||||
}
|
||||
if (host != null) {
|
||||
sb.append(encodeHost(host, encoding));
|
||||
}
|
||||
if (port != null) {
|
||||
sb.append(':');
|
||||
sb.append(encodePort(port, encoding));
|
||||
}
|
||||
}
|
||||
if (authority != null) {
|
||||
sb.append("//");
|
||||
if (userInfo != null) {
|
||||
sb.append(encodeUserInfo(userInfo, encoding));
|
||||
sb.append('@');
|
||||
}
|
||||
if (host != null) {
|
||||
sb.append(encodeHost(host, encoding));
|
||||
}
|
||||
if (port != null) {
|
||||
sb.append(':');
|
||||
sb.append(encodePort(port, encoding));
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(encodePath(path, encoding));
|
||||
sb.append(encodePath(path, encoding));
|
||||
|
||||
if (query != null) {
|
||||
sb.append('?');
|
||||
sb.append(encodeQuery(query, encoding));
|
||||
}
|
||||
if (query != null) {
|
||||
sb.append('?');
|
||||
sb.append(encodeQuery(query, encoding));
|
||||
}
|
||||
|
||||
if (fragment != null) {
|
||||
sb.append('#');
|
||||
sb.append(encodeFragment(fragment, encoding));
|
||||
}
|
||||
if (fragment != null) {
|
||||
sb.append('#');
|
||||
sb.append(encodeFragment(fragment, encoding));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -213,7 +217,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 +229,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 +241,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 +253,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 +265,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 +277,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 +289,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 +301,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 +313,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 +325,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -321,11 +335,11 @@ public abstract class UriUtils {
|
||||
/**
|
||||
* Decodes the given encoded source String into an URI. Based on the following rules:
|
||||
* <ul>
|
||||
* <li>Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, and
|
||||
* {@code "0"} through {@code "9"} stay the same.</li>
|
||||
* <li>Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.</li>
|
||||
* <li>A sequence "<code>%<i>xy</i></code>" is interpreted as a hexadecimal representation of the character.</li>
|
||||
* </ul>
|
||||
* <li>Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, and
|
||||
* {@code "0"} through {@code "9"} stay the same.</li>
|
||||
* <li>Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.</li>
|
||||
* <li>A sequence "<code>%<i>xy</i></code>" is interpreted as a hexadecimal representation of the character.</li>
|
||||
* </ul>
|
||||
* @param source the source string
|
||||
* @param encoding the encoding
|
||||
* @return the decoded URI
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
package org.springframework.web.util;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
@@ -26,21 +23,26 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/** @author Arjen Poutsma */
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
*/
|
||||
public class UriComponentsBuilderTests {
|
||||
|
||||
@Test
|
||||
public void plain() throws URISyntaxException {
|
||||
UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
|
||||
UriComponents result = builder.scheme("http").host("example.com").path("foo").queryParam("bar").fragment("baz").build();
|
||||
assertEquals("http", result.getScheme());
|
||||
assertEquals("example.com", result.getHost());
|
||||
assertEquals("foo", result.getPath());
|
||||
assertEquals("bar", result.getQuery());
|
||||
assertEquals("baz", result.getFragment());
|
||||
assertEquals("http", result.getScheme());
|
||||
assertEquals("example.com", result.getHost());
|
||||
assertEquals("foo", result.getPath());
|
||||
assertEquals("bar", result.getQuery());
|
||||
assertEquals("baz", result.getFragment());
|
||||
|
||||
URI expected = new URI("http://example.com/foo?bar#baz");
|
||||
assertEquals("Invalid result URI", expected, result.toUri());
|
||||
@@ -49,43 +51,54 @@ public class UriComponentsBuilderTests {
|
||||
@Test
|
||||
public void fromPath() throws URISyntaxException {
|
||||
UriComponents result = UriComponentsBuilder.fromPath("foo").queryParam("bar").fragment("baz").build();
|
||||
assertEquals("foo", result.getPath());
|
||||
assertEquals("bar", result.getQuery());
|
||||
assertEquals("baz", result.getFragment());
|
||||
assertEquals("foo", result.getPath());
|
||||
assertEquals("bar", result.getQuery());
|
||||
assertEquals("baz", result.getFragment());
|
||||
|
||||
URI expected = new URI("/foo?bar#baz");
|
||||
assertEquals("Invalid result URI", expected, result.toUri());
|
||||
|
||||
result = UriComponentsBuilder.fromPath("/foo").build();
|
||||
assertEquals("/foo", result.getPath());
|
||||
assertEquals("/foo", result.getPath());
|
||||
|
||||
expected = new URI("/foo");
|
||||
expected = new URI("/foo");
|
||||
assertEquals("Invalid result URI", expected, result.toUri());
|
||||
}
|
||||
|
||||
@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());
|
||||
assertEquals("example.com", result.getHost());
|
||||
assertEquals("/foo", result.getPath());
|
||||
assertEquals("bar", result.getQuery());
|
||||
assertEquals("baz", result.getFragment());
|
||||
UriComponents result = UriComponentsBuilder.fromUri(uri).build();
|
||||
assertEquals("http", result.getScheme());
|
||||
assertEquals("example.com", result.getHost());
|
||||
assertEquals("/foo", result.getPath());
|
||||
assertEquals("bar", result.getQuery());
|
||||
assertEquals("baz", result.getFragment());
|
||||
|
||||
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
|
||||
public void fromUriEncodedQuery() throws URISyntaxException {
|
||||
URI uri = new URI("http://www.example.org/?param=aGVsbG9Xb3JsZA%3D%3D");
|
||||
String fromUri = UriComponentsBuilder.fromUri(uri).build().getQueryParams().get("param").get(0);
|
||||
String fromUriString = UriComponentsBuilder.fromUriString(uri.toString()).build().getQueryParams().get("param").get(0);
|
||||
@Test
|
||||
public void fromUriEncodedQuery() throws URISyntaxException {
|
||||
URI uri = new URI("http://www.example.org/?param=aGVsbG9Xb3JsZA%3D%3D");
|
||||
String fromUri = UriComponentsBuilder.fromUri(uri).build().getQueryParams().get("param").get(0);
|
||||
String fromUriString = UriComponentsBuilder.fromUriString(uri.toString()).build().getQueryParams().get("param").get(0);
|
||||
|
||||
assertEquals(fromUri, fromUriString);
|
||||
}
|
||||
assertEquals(fromUri, fromUriString);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromUriString() {
|
||||
@@ -102,34 +115,35 @@ public class UriComponentsBuilderTests {
|
||||
result = UriComponentsBuilder.fromUriString(
|
||||
"http://arjen:foobar@java.sun.com:80/javase/6/docs/api/java/util/BitSet.html?foo=bar#and(java.util.BitSet)")
|
||||
.build();
|
||||
assertEquals("http", result.getScheme());
|
||||
assertEquals("arjen:foobar", result.getUserInfo());
|
||||
assertEquals("java.sun.com", result.getHost());
|
||||
assertEquals(80, result.getPort());
|
||||
assertEquals("/javase/6/docs/api/java/util/BitSet.html", result.getPath());
|
||||
assertEquals("foo=bar", result.getQuery());
|
||||
assertEquals("http", result.getScheme());
|
||||
assertEquals("arjen:foobar", result.getUserInfo());
|
||||
assertEquals("java.sun.com", result.getHost());
|
||||
assertEquals(80, result.getPort());
|
||||
assertEquals("/javase/6/docs/api/java/util/BitSet.html", result.getPath());
|
||||
assertEquals("foo=bar", result.getQuery());
|
||||
MultiValueMap<String, String> expectedQueryParams = new LinkedMultiValueMap<String, String>(1);
|
||||
expectedQueryParams.add("foo", "bar");
|
||||
assertEquals(expectedQueryParams, result.getQueryParams());
|
||||
assertEquals("and(java.util.BitSet)", result.getFragment());
|
||||
assertEquals("and(java.util.BitSet)", result.getFragment());
|
||||
|
||||
result = UriComponentsBuilder.fromUriString("mailto:java-net@java.sun.com").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));
|
||||
assertNull(result.getQuery());
|
||||
assertNull(result.getFragment());
|
||||
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.getSchemeSpecificPart());
|
||||
assertNull(result.getPath());
|
||||
assertNull(result.getQuery());
|
||||
assertEquals("baz", result.getFragment());
|
||||
|
||||
result = UriComponentsBuilder.fromUriString("docs/guide/collections/designfaq.html#28").build();
|
||||
assertNull(result.getScheme());
|
||||
assertNull(result.getUserInfo());
|
||||
assertNull(result.getHost());
|
||||
assertEquals(-1, result.getPort());
|
||||
assertEquals("docs/guide/collections/designfaq.html", result.getPath());
|
||||
assertNull(result.getQuery());
|
||||
assertEquals("28", result.getFragment());
|
||||
result = UriComponentsBuilder.fromUriString("docs/guide/collections/designfaq.html#28").build();
|
||||
assertNull(result.getScheme());
|
||||
assertNull(result.getUserInfo());
|
||||
assertNull(result.getHost());
|
||||
assertEquals(-1, result.getPort());
|
||||
assertEquals("docs/guide/collections/designfaq.html", result.getPath());
|
||||
assertNull(result.getQuery());
|
||||
assertEquals("28", result.getFragment());
|
||||
}
|
||||
|
||||
|
||||
@@ -265,7 +279,7 @@ public class UriComponentsBuilderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildAndExpand() {
|
||||
public void buildAndExpandHierarchical() {
|
||||
UriComponents result = UriComponentsBuilder.fromPath("/{foo}").buildAndExpand("fooValue");
|
||||
assertEquals("/fooValue", result.toUriString());
|
||||
|
||||
@@ -275,4 +289,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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user