SPR-5973: UriBuilder
This commit is contained in:
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
* Copyright 2002-2011 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.util;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Builder for {@link URI} objects.
|
||||
*
|
||||
* <p>Typical usage involves:
|
||||
* <ol>
|
||||
* <li>Create a {@code UriBuilder} with one of the static factory methods (such as {@link #fromPath(String)} or
|
||||
* {@link #fromUri(URI)})</li>
|
||||
* <li>Set the various URI components through the respective methods ({@link #scheme(String)},
|
||||
* {@link #userInfo(String)}, {@link #host(String)}, {@link #port(int)}, {@link #path(String)},
|
||||
* {@link #pathSegment(String...)}, {@link #queryParam(String, Object...)}, and {@link #fragment(String)}.</li>
|
||||
* <li>Build the URI with one of the {@link #build} method variants.</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>Most of the URI component methods accept URI template variables (i.e. {@code "{foo}"}), which are expanded by
|
||||
* calling {@code build}.
|
||||
* one , are allowed in most components of a URI but their value is
|
||||
* restricted to a particular component. E.g.
|
||||
* <blockquote><code>UriBuilder.fromPath("{arg1}").build("foo#bar");</code></blockquote>
|
||||
* would result in encoding of the '#' such that the resulting URI is
|
||||
* "foo%23bar". To create a URI "foo#bar" use
|
||||
* <blockquote><code>UriBuilder.fromPath("{arg1}").fragment("{arg2}").build("foo", "bar")</code></blockquote>
|
||||
* instead. URI template names and delimiters are never encoded but their
|
||||
* values are encoded when a URI is built.
|
||||
* Template parameter regular expressions are ignored when building a URI, i.e.
|
||||
* no validation is performed.
|
||||
* <p>Inspired by {@link javax.ws.rs.core.UriBuilder}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @since 3.1
|
||||
* @see #newInstance()
|
||||
* @see #fromPath(String)
|
||||
* @see #fromUri(URI)
|
||||
*/
|
||||
public class UriBuilder {
|
||||
|
||||
private String scheme;
|
||||
|
||||
private String userInfo;
|
||||
|
||||
private String host;
|
||||
|
||||
private int port = -1;
|
||||
|
||||
private final List<String> pathSegments = new ArrayList<String>();
|
||||
|
||||
private final StringBuilder queryBuilder = new StringBuilder();
|
||||
|
||||
private String fragment;
|
||||
|
||||
/**
|
||||
* Default constructor. Protected to prevent direct instantiation.
|
||||
*
|
||||
* @see #newInstance()
|
||||
* @see #fromPath(String)
|
||||
* @see #fromUri(URI)
|
||||
*/
|
||||
protected UriBuilder() {
|
||||
}
|
||||
|
||||
// Factory methods
|
||||
|
||||
/**
|
||||
* Returns a new, empty URI builder.
|
||||
*
|
||||
* @return the new {@code UriBuilder}
|
||||
*/
|
||||
public static UriBuilder newInstance() {
|
||||
return new UriBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URI builder that is initialized with the given path.
|
||||
*
|
||||
* @param path the path to initialize with
|
||||
* @return the new {@code UriBuilder}
|
||||
*/
|
||||
public static UriBuilder fromPath(String path) {
|
||||
UriBuilder builder = new UriBuilder();
|
||||
builder.path(path);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URI builder that is initialized with the given {@code URI}.
|
||||
*
|
||||
* @param uri the URI to initialize with
|
||||
* @return the new {@code UriBuilder}
|
||||
*/
|
||||
public static UriBuilder fromUri(URI uri) {
|
||||
UriBuilder builder = new UriBuilder();
|
||||
builder.uri(uri);
|
||||
return builder;
|
||||
}
|
||||
|
||||
// build methods
|
||||
|
||||
/**
|
||||
* Builds a URI with no URI template variables. Any template variable definitions found will be encoded (i.e.
|
||||
* {@code "/{foo}"} will result in {@code "/%7Bfoo%7D"}.
|
||||
* @return the resulting URI
|
||||
*/
|
||||
public URI build() {
|
||||
StringBuilder uriBuilder = new StringBuilder();
|
||||
|
||||
if (scheme != null) {
|
||||
uriBuilder.append(scheme).append(':');
|
||||
}
|
||||
|
||||
if (userInfo != null || host != null || port != -1) {
|
||||
uriBuilder.append("//");
|
||||
|
||||
if (StringUtils.hasLength(userInfo)) {
|
||||
uriBuilder.append(userInfo);
|
||||
uriBuilder.append('@');
|
||||
}
|
||||
|
||||
if (host != null) {
|
||||
uriBuilder.append(host);
|
||||
}
|
||||
|
||||
if (port != -1) {
|
||||
uriBuilder.append(':');
|
||||
uriBuilder.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pathSegments.isEmpty()) {
|
||||
for (String pathSegment : pathSegments) {
|
||||
boolean startsWithSlash = pathSegment.charAt(0) == '/';
|
||||
boolean endsWithSlash = uriBuilder.length() > 0 && uriBuilder.charAt(uriBuilder.length() - 1) == '/';
|
||||
|
||||
if (!endsWithSlash && !startsWithSlash) {
|
||||
uriBuilder.append('/');
|
||||
}
|
||||
else if (endsWithSlash && startsWithSlash) {
|
||||
pathSegment = pathSegment.substring(1);
|
||||
}
|
||||
uriBuilder.append(pathSegment);
|
||||
}
|
||||
}
|
||||
|
||||
if (queryBuilder.length() > 0) {
|
||||
uriBuilder.append('?');
|
||||
uriBuilder.append(queryBuilder);
|
||||
}
|
||||
|
||||
if (StringUtils.hasLength(fragment)) {
|
||||
uriBuilder.append('#');
|
||||
uriBuilder.append(fragment);
|
||||
}
|
||||
String uri = uriBuilder.toString();
|
||||
|
||||
uri = StringUtils.replace(uri, "{", "%7B");
|
||||
uri = StringUtils.replace(uri, "}", "%7D");
|
||||
|
||||
return URI.create(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a URI with the given URI template variables. Any template variable definitions found will be expanded with
|
||||
* the given variables map. All variable values will be encoded in accordance with the encoding rules for the URI
|
||||
* component they occur in.
|
||||
*
|
||||
* @param uriVariables the map of URI variables
|
||||
* @return the resulting URI
|
||||
*/
|
||||
public URI build(Map<String, ?> uriVariables) {
|
||||
return buildFromMap(true, uriVariables);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a URI with the given URI template variables. Any template variable definitions found will be expanded with the
|
||||
* given variables map. All variable values will not be encoded.
|
||||
*
|
||||
* @param uriVariables the map of URI variables
|
||||
* @return the resulting URI
|
||||
*/
|
||||
public URI buildFromEncoded(Map<String, ?> uriVariables) {
|
||||
return buildFromMap(false, uriVariables);
|
||||
}
|
||||
|
||||
private URI buildFromMap(boolean encodeUriVariableValues, Map<String, ?> uriVariables) {
|
||||
if (CollectionUtils.isEmpty(uriVariables)) {
|
||||
return build();
|
||||
}
|
||||
|
||||
StringBuilder uriBuilder = new StringBuilder();
|
||||
|
||||
UriTemplate template;
|
||||
|
||||
if (scheme != null) {
|
||||
template = new UriTemplate(scheme, UriUtils.SCHEME_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariables));
|
||||
uriBuilder.append(':');
|
||||
}
|
||||
|
||||
if (userInfo != null || host != null || port != -1) {
|
||||
uriBuilder.append("//");
|
||||
|
||||
if (StringUtils.hasLength(userInfo)) {
|
||||
template = new UriTemplate(userInfo, UriUtils.USER_INFO_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariables));
|
||||
uriBuilder.append('@');
|
||||
}
|
||||
|
||||
if (host != null) {
|
||||
template = new UriTemplate(host, UriUtils.HOST_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariables));
|
||||
}
|
||||
|
||||
if (port != -1) {
|
||||
uriBuilder.append(':');
|
||||
uriBuilder.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pathSegments.isEmpty()) {
|
||||
for (String pathSegment : pathSegments) {
|
||||
boolean startsWithSlash = pathSegment.charAt(0) == '/';
|
||||
boolean endsWithSlash = uriBuilder.length() > 0 && uriBuilder.charAt(uriBuilder.length() - 1) == '/';
|
||||
|
||||
if (!endsWithSlash && !startsWithSlash) {
|
||||
uriBuilder.append('/');
|
||||
}
|
||||
else if (endsWithSlash && startsWithSlash) {
|
||||
pathSegment = pathSegment.substring(1);
|
||||
}
|
||||
template = new UriTemplate(pathSegment, UriUtils.PATH_SEGMENT_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariables));
|
||||
}
|
||||
}
|
||||
if (queryBuilder.length() > 0) {
|
||||
uriBuilder.append('?');
|
||||
template = new UriTemplate(queryBuilder.toString(), UriUtils.QUERY_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariables));
|
||||
}
|
||||
|
||||
if (StringUtils.hasLength(fragment)) {
|
||||
uriBuilder.append('#');
|
||||
template = new UriTemplate(fragment, UriUtils.FRAGMENT_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariables));
|
||||
}
|
||||
|
||||
return URI.create(uriBuilder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a URI with the given URI template variable values. Any template variable definitions found will be expanded
|
||||
* with the given variables. All variable values will be encoded in accordance with the encoding rules for the URI
|
||||
* component they occur in.
|
||||
*
|
||||
* @param uriVariableValues the array of URI variables
|
||||
* @return the resulting URI
|
||||
*/
|
||||
public URI build(Object... uriVariableValues) {
|
||||
return buildFromVarArg(true, uriVariableValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a URI with the given URI template variable values. Any template variable definitions found will be expanded
|
||||
* with the given variables. All variable values will not be encoded.
|
||||
*
|
||||
* @param uriVariableValues the array of URI variables
|
||||
* @return the resulting URI
|
||||
*/
|
||||
public URI buildFromEncoded(Object... uriVariableValues) {
|
||||
return buildFromVarArg(false, uriVariableValues);
|
||||
}
|
||||
|
||||
private URI buildFromVarArg(boolean encodeUriVariableValues, Object... uriVariableValues) {
|
||||
if (ObjectUtils.isEmpty(uriVariableValues)) {
|
||||
return build();
|
||||
}
|
||||
|
||||
StringBuilder uriBuilder = new StringBuilder();
|
||||
|
||||
UriTemplate template;
|
||||
|
||||
if (scheme != null) {
|
||||
template = new UriTemplate(scheme, UriUtils.SCHEME_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariableValues));
|
||||
uriBuilder.append(':');
|
||||
}
|
||||
|
||||
if (userInfo != null || host != null || port != -1) {
|
||||
uriBuilder.append("//");
|
||||
|
||||
if (StringUtils.hasLength(userInfo)) {
|
||||
template = new UriTemplate(userInfo, UriUtils.USER_INFO_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariableValues));
|
||||
uriBuilder.append('@');
|
||||
}
|
||||
|
||||
if (host != null) {
|
||||
template = new UriTemplate(host, UriUtils.HOST_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariableValues));
|
||||
}
|
||||
|
||||
if (port != -1) {
|
||||
uriBuilder.append(':');
|
||||
uriBuilder.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pathSegments.isEmpty()) {
|
||||
for (String pathSegment : pathSegments) {
|
||||
boolean startsWithSlash = pathSegment.charAt(0) == '/';
|
||||
boolean endsWithSlash = uriBuilder.length() > 0 && uriBuilder.charAt(uriBuilder.length() - 1) == '/';
|
||||
|
||||
if (!endsWithSlash && !startsWithSlash) {
|
||||
uriBuilder.append('/');
|
||||
}
|
||||
else if (endsWithSlash && startsWithSlash) {
|
||||
pathSegment = pathSegment.substring(1);
|
||||
}
|
||||
template = new UriTemplate(pathSegment, UriUtils.PATH_SEGMENT_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariableValues));
|
||||
}
|
||||
}
|
||||
|
||||
if (queryBuilder.length() > 0) {
|
||||
uriBuilder.append('?');
|
||||
template = new UriTemplate(queryBuilder.toString(), UriUtils.QUERY_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariableValues));
|
||||
}
|
||||
|
||||
if (StringUtils.hasLength(fragment)) {
|
||||
uriBuilder.append('#');
|
||||
template = new UriTemplate(fragment, UriUtils.FRAGMENT_COMPONENT);
|
||||
uriBuilder.append(template.expandAsString(encodeUriVariableValues, uriVariableValues));
|
||||
}
|
||||
|
||||
return URI.create(uriBuilder.toString());
|
||||
}
|
||||
|
||||
// URI components methods
|
||||
|
||||
/**
|
||||
* Initializes all components of this URI builder with the components of the given URI.
|
||||
*
|
||||
* @param uri the URI
|
||||
* @return this UriBuilder
|
||||
*/
|
||||
public UriBuilder 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.getHost() != null) {
|
||||
this.host = uri.getHost();
|
||||
}
|
||||
if (uri.getPort() != -1) {
|
||||
this.port = uri.getPort();
|
||||
}
|
||||
if (StringUtils.hasLength(uri.getRawPath())) {
|
||||
String[] pathSegments = StringUtils.tokenizeToStringArray(uri.getRawPath(), "/");
|
||||
|
||||
this.pathSegments.clear();
|
||||
Collections.addAll(this.pathSegments, pathSegments);
|
||||
}
|
||||
if (StringUtils.hasLength(uri.getRawQuery())) {
|
||||
this.queryBuilder.setLength(0);
|
||||
this.queryBuilder.append(uri.getRawQuery());
|
||||
}
|
||||
if (uri.getRawFragment() != null) {
|
||||
this.fragment = uri.getRawFragment();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return this UriBuilder
|
||||
*/
|
||||
public UriBuilder scheme(String scheme) {
|
||||
if (scheme != null) {
|
||||
Assert.hasLength(scheme, "'scheme' must not be empty");
|
||||
this.scheme = UriUtils.encode(scheme, UriUtils.SCHEME_COMPONENT, true);
|
||||
}
|
||||
else {
|
||||
this.scheme = null;
|
||||
}
|
||||
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 UriBuilder
|
||||
*/
|
||||
public UriBuilder userInfo(String userInfo) {
|
||||
if (userInfo != null) {
|
||||
Assert.hasLength(userInfo, "'userInfo' must not be empty");
|
||||
this.userInfo = UriUtils.encode(userInfo, UriUtils.USER_INFO_COMPONENT, true);
|
||||
}
|
||||
else {
|
||||
this.userInfo = null;
|
||||
}
|
||||
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.
|
||||
*
|
||||
* @param host the URI host
|
||||
* @return this UriBuilder
|
||||
*/
|
||||
public UriBuilder host(String host) {
|
||||
if (host != null) {
|
||||
Assert.hasLength(host, "'host' must not be empty");
|
||||
this.host = UriUtils.encode(host, UriUtils.HOST_COMPONENT, true);
|
||||
}
|
||||
else {
|
||||
this.host = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the URI port. Passing {@code -1} will clear the port of this builder.
|
||||
*
|
||||
* @param port the URI port
|
||||
* @return this UriBuilder
|
||||
*/
|
||||
public UriBuilder port(int port) {
|
||||
Assert.isTrue(port >= -1, "'port' must not be < -1");
|
||||
this.port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 UriBuilder
|
||||
*/
|
||||
public UriBuilder path(String path) {
|
||||
Assert.notNull(path, "path must not be null");
|
||||
|
||||
String[] pathSegments = StringUtils.tokenizeToStringArray(path, "/");
|
||||
return pathSegment(pathSegments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the given path segments to the existing path of this builder. Each given path segments may contain URI
|
||||
* template variables.
|
||||
*
|
||||
* @param segments the URI path segments
|
||||
* @return this UriBuilder
|
||||
*/
|
||||
public UriBuilder pathSegment(String... segments) throws IllegalArgumentException {
|
||||
Assert.notNull(segments, "'segments' must not be null");
|
||||
for (String segment : segments) {
|
||||
this.pathSegments.add(UriUtils.encode(segment, UriUtils.PATH_SEGMENT_COMPONENT, true));
|
||||
}
|
||||
|
||||
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}.
|
||||
*
|
||||
* @param name the query parameter name
|
||||
* @param values the query parameter values
|
||||
* @return this UriBuilder
|
||||
*/
|
||||
public UriBuilder queryParam(String name, Object... values) {
|
||||
Assert.notNull(name, "'name' must not be null");
|
||||
|
||||
String encodedName = UriUtils.encode(name, UriUtils.QUERY_PARAM_COMPONENT, true);
|
||||
|
||||
if (ObjectUtils.isEmpty(values)) {
|
||||
if (queryBuilder.length() != 0) {
|
||||
queryBuilder.append('&');
|
||||
}
|
||||
queryBuilder.append(encodedName);
|
||||
}
|
||||
else {
|
||||
for (Object value : values) {
|
||||
if (queryBuilder.length() != 0) {
|
||||
queryBuilder.append('&');
|
||||
}
|
||||
queryBuilder.append(encodedName);
|
||||
|
||||
String valueAsString = value != null ? value.toString() : "";
|
||||
if (valueAsString.length() != 0) {
|
||||
queryBuilder.append('=');
|
||||
queryBuilder.append(UriUtils.encode(valueAsString, UriUtils.QUERY_PARAM_COMPONENT, true));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
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.
|
||||
*
|
||||
* @param fragment the URI fragment
|
||||
* @return this UriBuilder
|
||||
*/
|
||||
public UriBuilder fragment(String fragment) {
|
||||
if (fragment != null) {
|
||||
Assert.hasLength(fragment, "'fragment' must not be empty");
|
||||
this.fragment = UriUtils.encode(fragment, UriUtils.FRAGMENT_COMPONENT, true);
|
||||
}
|
||||
else {
|
||||
this.fragment = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2011 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.
|
||||
@@ -47,7 +47,7 @@ public class UriTemplate implements Serializable {
|
||||
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
|
||||
|
||||
/** Replaces template variables in the URI template. */
|
||||
private static final String VALUE_REGEX = "(.*)";
|
||||
private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";
|
||||
|
||||
|
||||
private final List<String> variableNames;
|
||||
@@ -56,6 +56,8 @@ public class UriTemplate implements Serializable {
|
||||
|
||||
private final String uriTemplate;
|
||||
|
||||
private final UriUtils.UriComponent uriComponent;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a new {@link UriTemplate} with the given URI String.
|
||||
@@ -66,6 +68,19 @@ public class UriTemplate implements Serializable {
|
||||
this.uriTemplate = uriTemplate;
|
||||
this.variableNames = parser.getVariableNames();
|
||||
this.matchPattern = parser.getMatchPattern();
|
||||
this.uriComponent = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new {@link UriTemplate} with the given URI String.
|
||||
* @param uriTemplate the URI template string
|
||||
*/
|
||||
public UriTemplate(String uriTemplate, UriUtils.UriComponent uriComponent) {
|
||||
Parser parser = new Parser(uriTemplate);
|
||||
this.uriTemplate = uriTemplate;
|
||||
this.variableNames = parser.getVariableNames();
|
||||
this.matchPattern = parser.getMatchPattern();
|
||||
this.uriComponent = uriComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,6 +110,28 @@ public class UriTemplate implements Serializable {
|
||||
* or if it does not contain values for all the variable names
|
||||
*/
|
||||
public URI expand(Map<String, ?> uriVariables) {
|
||||
return encodeUri(expandAsString(true, uriVariables));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the Map of variables, expands this template into a URI. The Map keys represent variable names,
|
||||
* the Map values variable values. The order of variables is not significant.
|
||||
* <p>Example:
|
||||
* <pre class="code">
|
||||
* UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
|
||||
* Map<String, String> uriVariables = new HashMap<String, String>();
|
||||
* uriVariables.put("booking", "42");
|
||||
* uriVariables.put("hotel", "1");
|
||||
* System.out.println(template.expand(uriVariables));
|
||||
* </pre>
|
||||
* will print: <blockquote><code>http://example.com/hotels/1/bookings/42</code></blockquote>
|
||||
* @param encodeUriVariableValues indicates whether uri template variables should be encoded or not
|
||||
* @param uriVariables the map of URI variables
|
||||
* @return the expanded URI
|
||||
* @throws IllegalArgumentException if <code>uriVariables</code> is <code>null</code>;
|
||||
* or if it does not contain values for all the variable names
|
||||
*/
|
||||
public String expandAsString(boolean encodeUriVariableValues, Map<String, ?> uriVariables) {
|
||||
Assert.notNull(uriVariables, "'uriVariables' must not be null");
|
||||
Object[] values = new Object[this.variableNames.size()];
|
||||
for (int i = 0; i < this.variableNames.size(); i++) {
|
||||
@@ -104,7 +141,7 @@ public class UriTemplate implements Serializable {
|
||||
}
|
||||
values[i] = uriVariables.get(name);
|
||||
}
|
||||
return expand(values);
|
||||
return expandAsString(encodeUriVariableValues, values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,22 +159,45 @@ public class UriTemplate implements Serializable {
|
||||
* or if it does not contain sufficient variables
|
||||
*/
|
||||
public URI expand(Object... uriVariableValues) {
|
||||
return encodeUri(expandAsString(true, uriVariableValues));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of variables, expand this template into a full URI String. The array represent variable values.
|
||||
* The order of variables is significant.
|
||||
* <p>Example:
|
||||
* <pre class="code">
|
||||
* UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
|
||||
* System.out.println(template.expand("1", "42));
|
||||
* </pre>
|
||||
* will print: <blockquote><code>http://example.com/hotels/1/bookings/42</code></blockquote>
|
||||
* @param encodeVariableValues indicates whether uri template variables should be encoded or not
|
||||
* @param uriVariableValues the array of URI variables
|
||||
* @return the expanded URI
|
||||
* @throws IllegalArgumentException if <code>uriVariables</code> is <code>null</code>
|
||||
* or if it does not contain sufficient variables
|
||||
*/
|
||||
public String expandAsString(boolean encodeVariableValues, Object... uriVariableValues) {
|
||||
Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null");
|
||||
if (uriVariableValues.length != this.variableNames.size()) {
|
||||
if (uriVariableValues.length < this.variableNames.size()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid amount of variables values in [" + this.uriTemplate + "]: expected " +
|
||||
"Not enough of variables values in [" + this.uriTemplate + "]: expected at least " +
|
||||
this.variableNames.size() + "; got " + uriVariableValues.length);
|
||||
}
|
||||
Matcher matcher = NAMES_PATTERN.matcher(this.uriTemplate);
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
StringBuffer uriBuffer = new StringBuffer();
|
||||
int i = 0;
|
||||
while (matcher.find()) {
|
||||
Object uriVariable = uriVariableValues[i++];
|
||||
String replacement = Matcher.quoteReplacement(uriVariable != null ? uriVariable.toString() : "");
|
||||
matcher.appendReplacement(buffer, replacement);
|
||||
String uriVariableString = uriVariable != null ? uriVariable.toString() : "";
|
||||
if (encodeVariableValues && uriComponent != null) {
|
||||
uriVariableString = UriUtils.encode(uriVariableString, uriComponent, false);
|
||||
}
|
||||
String replacement = Matcher.quoteReplacement(uriVariableString);
|
||||
matcher.appendReplacement(uriBuffer, replacement);
|
||||
}
|
||||
matcher.appendTail(buffer);
|
||||
return encodeUri(buffer.toString());
|
||||
matcher.appendTail(uriBuffer);
|
||||
return uriBuffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,8 +280,23 @@ public class UriTemplate implements Serializable {
|
||||
int end = 0;
|
||||
while (m.find()) {
|
||||
this.patternBuilder.append(quote(uriTemplate, end, m.start()));
|
||||
this.patternBuilder.append(VALUE_REGEX);
|
||||
this.variableNames.add(m.group(1));
|
||||
String match = m.group(1);
|
||||
int colonIdx = match.indexOf(':');
|
||||
if (colonIdx == -1) {
|
||||
this.patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
|
||||
this.variableNames.add(match);
|
||||
}
|
||||
else {
|
||||
if (colonIdx + 1 == match.length()) {
|
||||
throw new IllegalArgumentException("No custom regular expression specified after ':' in \"" + match + "\"");
|
||||
}
|
||||
String variablePattern = match.substring(colonIdx + 1, match.length());
|
||||
this.patternBuilder.append('(');
|
||||
this.patternBuilder.append(variablePattern);
|
||||
this.patternBuilder.append(')');
|
||||
String variableName = match.substring(0, colonIdx);
|
||||
this.variableNames.add(variableName);
|
||||
}
|
||||
end = m.end();
|
||||
}
|
||||
this.patternBuilder.append(quote(uriTemplate, end, uriTemplate.length()));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2011 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.
|
||||
@@ -18,7 +18,6 @@ package org.springframework.web.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.BitSet;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -41,23 +40,7 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public abstract class UriUtils {
|
||||
|
||||
private static final BitSet SCHEME;
|
||||
|
||||
private static final BitSet USER_INFO;
|
||||
|
||||
private static final BitSet HOST;
|
||||
|
||||
private static final BitSet PORT;
|
||||
|
||||
private static final BitSet PATH;
|
||||
|
||||
private static final BitSet SEGMENT;
|
||||
|
||||
private static final BitSet QUERY;
|
||||
|
||||
private static final BitSet QUERY_PARAM;
|
||||
|
||||
private static final BitSet FRAGMENT;
|
||||
private static final String DEFAULT_ENCODING = "UTF-8";
|
||||
|
||||
private static final String SCHEME_PATTERN = "([^:/?#]+):";
|
||||
|
||||
@@ -84,107 +67,7 @@ public abstract class UriUtils {
|
||||
"^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" +
|
||||
")?" + PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
|
||||
|
||||
|
||||
static {
|
||||
// variable names refer to RFC 3986, appendix A
|
||||
BitSet alpha = new BitSet(256);
|
||||
for (int i = 'a'; i <= 'z'; i++) {
|
||||
alpha.set(i);
|
||||
}
|
||||
for (int i = 'A'; i <= 'Z'; i++) {
|
||||
alpha.set(i);
|
||||
}
|
||||
BitSet digit = new BitSet(256);
|
||||
for (int i = '0'; i <= '9'; i++) {
|
||||
digit.set(i);
|
||||
}
|
||||
|
||||
BitSet gendelims = new BitSet(256);
|
||||
gendelims.set(':');
|
||||
gendelims.set('/');
|
||||
gendelims.set('?');
|
||||
gendelims.set('#');
|
||||
gendelims.set('[');
|
||||
gendelims.set(']');
|
||||
gendelims.set('@');
|
||||
|
||||
BitSet subdelims = new BitSet(256);
|
||||
subdelims.set('!');
|
||||
subdelims.set('$');
|
||||
subdelims.set('&');
|
||||
subdelims.set('\'');
|
||||
subdelims.set('(');
|
||||
subdelims.set(')');
|
||||
subdelims.set('*');
|
||||
subdelims.set('+');
|
||||
subdelims.set(',');
|
||||
subdelims.set(';');
|
||||
subdelims.set('=');
|
||||
|
||||
BitSet reserved = new BitSet(256);
|
||||
reserved.or(gendelims);
|
||||
reserved.or(subdelims);
|
||||
|
||||
BitSet unreserved = new BitSet(256);
|
||||
unreserved.or(alpha);
|
||||
unreserved.or(digit);
|
||||
unreserved.set('-');
|
||||
unreserved.set('.');
|
||||
unreserved.set('_');
|
||||
unreserved.set('~');
|
||||
|
||||
SCHEME = new BitSet(256);
|
||||
SCHEME.or(alpha);
|
||||
SCHEME.or(digit);
|
||||
SCHEME.set('+');
|
||||
SCHEME.set('-');
|
||||
SCHEME.set('.');
|
||||
|
||||
USER_INFO = new BitSet(256);
|
||||
USER_INFO.or(unreserved);
|
||||
USER_INFO.or(subdelims);
|
||||
USER_INFO.set(':');
|
||||
|
||||
HOST = new BitSet(256);
|
||||
HOST.or(unreserved);
|
||||
HOST.or(subdelims);
|
||||
|
||||
PORT = new BitSet(256);
|
||||
PORT.or(digit);
|
||||
|
||||
BitSet pchar = new BitSet(256);
|
||||
pchar.or(unreserved);
|
||||
pchar.or(subdelims);
|
||||
pchar.set(':');
|
||||
pchar.set('@');
|
||||
|
||||
SEGMENT = new BitSet(256);
|
||||
SEGMENT.or(pchar);
|
||||
|
||||
PATH = new BitSet(256);
|
||||
PATH.or(SEGMENT);
|
||||
PATH.set('/');
|
||||
|
||||
QUERY = new BitSet(256);
|
||||
QUERY.or(pchar);
|
||||
QUERY.set('/');
|
||||
QUERY.set('?');
|
||||
|
||||
QUERY_PARAM = new BitSet(256);
|
||||
QUERY_PARAM.or(pchar);
|
||||
QUERY_PARAM.set('/');
|
||||
QUERY_PARAM.set('?');
|
||||
QUERY_PARAM.clear('=');
|
||||
QUERY_PARAM.clear('+');
|
||||
QUERY_PARAM.clear('&');
|
||||
|
||||
FRAGMENT = new BitSet(256);
|
||||
FRAGMENT.or(pchar);
|
||||
FRAGMENT.set('/');
|
||||
FRAGMENT.set('?');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Encodes the given source URI into an encoded String. All various URI components
|
||||
* are encoded according to their respective valid character sets.
|
||||
@@ -246,6 +129,38 @@ public abstract class UriUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given source URI components into an encoded String.
|
||||
* All various URI components are optional, but encoded according
|
||||
* to their respective valid character sets.
|
||||
* @param scheme the scheme
|
||||
* @param authority the authority
|
||||
* @param userinfo the user info
|
||||
* @param host the host
|
||||
* @param port the port
|
||||
* @param path the path
|
||||
* @param query the query
|
||||
* @param fragment the fragment
|
||||
* @return the encoded URI
|
||||
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
|
||||
*/
|
||||
public static String encodeUriComponents(String scheme,
|
||||
String authority,
|
||||
String userinfo,
|
||||
String host,
|
||||
String port,
|
||||
String path,
|
||||
String query,
|
||||
String fragment) {
|
||||
try {
|
||||
return encodeUriComponents(scheme, authority, userinfo, host, port, path, query, fragment,
|
||||
DEFAULT_ENCODING);
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new InternalError("'UTF-8' encoding not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given source URI components into an encoded String.
|
||||
* All various URI components are optional, but encoded according
|
||||
@@ -263,10 +178,16 @@ public abstract class UriUtils {
|
||||
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeUriComponents(String scheme, String authority, String userinfo,
|
||||
String host, String port, String path, String query, String fragment, String encoding)
|
||||
throws UnsupportedEncodingException {
|
||||
|
||||
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();
|
||||
|
||||
@@ -275,7 +196,7 @@ public abstract class UriUtils {
|
||||
sb.append(':');
|
||||
}
|
||||
|
||||
if (authority != null) {
|
||||
if (userinfo != null || host != null || port != null) {
|
||||
sb.append("//");
|
||||
if (userinfo != null) {
|
||||
sb.append(encodeUserInfo(userinfo, encoding));
|
||||
@@ -288,9 +209,14 @@ public abstract class UriUtils {
|
||||
sb.append(':');
|
||||
sb.append(encodePort(port, encoding));
|
||||
}
|
||||
} else if (authority != null) {
|
||||
sb.append("//");
|
||||
sb.append(encodeAuthority(authority, encoding));
|
||||
}
|
||||
|
||||
sb.append(encodePath(path, encoding));
|
||||
if (path != null) {
|
||||
sb.append(encodePath(path, encoding));
|
||||
}
|
||||
|
||||
if (query != null) {
|
||||
sb.append('?');
|
||||
@@ -306,129 +232,194 @@ public abstract class UriUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given URI scheme.
|
||||
* Encodes the given URI scheme with the given encoding.
|
||||
* @param scheme the scheme to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded scheme
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeScheme(String scheme, String encoding) throws UnsupportedEncodingException {
|
||||
return encode(scheme, encoding, SCHEME);
|
||||
return encode(scheme, encoding, SCHEME_COMPONENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given URI user info.
|
||||
* Encodes the given URI authority with the given encoding.
|
||||
* @param authority the authority to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded authority
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeAuthority(String authority, String encoding) throws UnsupportedEncodingException {
|
||||
return encode(authority, encoding, AUTHORITY_COMPONENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given URI user info with the given encoding.
|
||||
* @param userInfo the user info to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded user info
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeUserInfo(String userInfo, String encoding) throws UnsupportedEncodingException {
|
||||
return encode(userInfo, encoding, USER_INFO);
|
||||
return encode(userInfo, encoding, USER_INFO_COMPONENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given URI host.
|
||||
* Encodes the given URI host with the given encoding.
|
||||
* @param host the host to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded host
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeHost(String host, String encoding) throws UnsupportedEncodingException {
|
||||
return encode(host, encoding, HOST);
|
||||
return encode(host, encoding, HOST_COMPONENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given URI port.
|
||||
* Encodes the given URI port with the given encoding.
|
||||
* @param port the port to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded port
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodePort(String port, String encoding) throws UnsupportedEncodingException {
|
||||
return encode(port, encoding, PORT);
|
||||
return encode(port, encoding, PORT_COMPONENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given URI path.
|
||||
* Encodes the given URI path with the given encoding.
|
||||
* @param path the path to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded path
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodePath(String path, String encoding) throws UnsupportedEncodingException {
|
||||
return encode(path, encoding, PATH);
|
||||
return encode(path, encoding, PATH_COMPONENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given URI path segment.
|
||||
* Encodes the given URI path segment with the given encoding.
|
||||
* @param segment the segment to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded segment
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodePathSegment(String segment, String encoding) throws UnsupportedEncodingException {
|
||||
return encode(segment, encoding, SEGMENT);
|
||||
return encode(segment, encoding, PATH_SEGMENT_COMPONENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given URI query.
|
||||
* Encodes the given URI query with the given encoding.
|
||||
* @param query the query to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded query
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeQuery(String query, String encoding) throws UnsupportedEncodingException {
|
||||
return encode(query, encoding, QUERY);
|
||||
return encode(query, encoding, QUERY_COMPONENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given URI query parameter.
|
||||
* Encodes the given URI query parameter with the given encoding.
|
||||
* @param queryParam the query parameter to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded query parameter
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeQueryParam(String queryParam, String encoding) throws UnsupportedEncodingException {
|
||||
return encode(queryParam, encoding, QUERY_PARAM);
|
||||
return encode(queryParam, encoding, QUERY_PARAM_COMPONENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the given URI fragment.
|
||||
* Encodes the given URI fragment with the given encoding.
|
||||
* @param fragment the fragment to be encoded
|
||||
* @param encoding the character encoding to encode to
|
||||
* @return the encoded fragment
|
||||
* @throws UnsupportedEncodingException when the given encoding parameter is not supported
|
||||
*/
|
||||
public static String encodeFragment(String fragment, String encoding) throws UnsupportedEncodingException {
|
||||
return encode(fragment, encoding, FRAGMENT);
|
||||
return encode(fragment, encoding, FRAGMENT_COMPONENT, false);
|
||||
}
|
||||
|
||||
private static String encode(String source, String encoding, BitSet notEncoded)
|
||||
throws UnsupportedEncodingException {
|
||||
/**
|
||||
* Encodes the given source into an encoded String using the rules specified by the given component. This method
|
||||
* encodes with the default encoding (i.e. UTF-8).
|
||||
* @param source the source string
|
||||
* @param uriComponent the URI component for the source
|
||||
* @param allowTemplateVars whether URI template variables are allowed. If {@code true}, '{' and '}' characters
|
||||
* are not encoded, even though they might not be valid for the component
|
||||
* @return the encoded URI
|
||||
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
|
||||
* @see #SCHEME_COMPONENT
|
||||
* @see #AUTHORITY_COMPONENT
|
||||
* @see #USER_INFO_COMPONENT
|
||||
* @see #HOST_COMPONENT
|
||||
* @see #PORT_COMPONENT
|
||||
* @see #PATH_COMPONENT
|
||||
* @see #PATH_SEGMENT_COMPONENT
|
||||
* @see #QUERY_COMPONENT
|
||||
* @see #QUERY_PARAM_COMPONENT
|
||||
* @see #FRAGMENT_COMPONENT
|
||||
*/
|
||||
public static String encode(String source, UriComponent uriComponent, boolean allowTemplateVars) {
|
||||
try {
|
||||
return encode(source, DEFAULT_ENCODING, uriComponent, allowTemplateVars);
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
throw new InternalError("'" + DEFAULT_ENCODING + "' encoding not supported");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.notNull(source, "'source' must not be null");
|
||||
/**
|
||||
* Encodes the given source into an encoded String using the rules specified by the given component.
|
||||
* @param source the source string
|
||||
* @param encoding the encoding of the source string
|
||||
* @param uriComponent the URI component for the source
|
||||
* @param allowTemplateVars whether URI template variables are allowed. If {@code true}, '{' and '}' characters
|
||||
* are not encoded, even though they might not be valid for the component
|
||||
* @return the encoded URI
|
||||
* @throws IllegalArgumentException when the given uri parameter is not a valid URI
|
||||
* @see #SCHEME_COMPONENT
|
||||
* @see #AUTHORITY_COMPONENT
|
||||
* @see #USER_INFO_COMPONENT
|
||||
* @see #HOST_COMPONENT
|
||||
* @see #PORT_COMPONENT
|
||||
* @see #PATH_COMPONENT
|
||||
* @see #PATH_SEGMENT_COMPONENT
|
||||
* @see #QUERY_COMPONENT
|
||||
* @see #QUERY_PARAM_COMPONENT
|
||||
* @see #FRAGMENT_COMPONENT
|
||||
*/
|
||||
public static String encode(String source, String encoding, UriComponent uriComponent, boolean allowTemplateVars)
|
||||
throws UnsupportedEncodingException {
|
||||
Assert.hasLength(encoding, "'encoding' must not be empty");
|
||||
|
||||
byte[] bytes = encode(source.getBytes(encoding), notEncoded);
|
||||
byte[] bytes = encodeInternal(source.getBytes(encoding), uriComponent, allowTemplateVars);
|
||||
return new String(bytes, "US-ASCII");
|
||||
}
|
||||
|
||||
private static byte[] encode(byte[] source, BitSet notEncoded) {
|
||||
private static byte[] encodeInternal(byte[] source, UriComponent uriComponent, boolean allowTemplateVars) {
|
||||
Assert.notNull(source, "'source' must not be null");
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length * 2);
|
||||
Assert.notNull(uriComponent, "'uriComponent' 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 (notEncoded.get(b)) {
|
||||
if (uriComponent.isAllowed(b)) {
|
||||
bos.write(b);
|
||||
}
|
||||
else if (allowTemplateVars && (b == '{' || 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);
|
||||
}
|
||||
@@ -436,6 +427,7 @@ public abstract class UriUtils {
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Decodes the given encoded source String into an URI. Based on the following
|
||||
* rules:
|
||||
@@ -486,4 +478,126 @@ public abstract class UriUtils {
|
||||
return changed ? new String(bos.toByteArray(), encoding) : source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the contract for an URI component, i.e. scheme, host, path, etc.
|
||||
*/
|
||||
public interface UriComponent {
|
||||
|
||||
/**
|
||||
* Specifies 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
|
||||
*/
|
||||
boolean isAllowed(int c);
|
||||
|
||||
}
|
||||
|
||||
private static abstract class AbstractUriComponent implements UriComponent {
|
||||
|
||||
protected boolean isAlpha(int c) {
|
||||
return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
|
||||
}
|
||||
|
||||
protected boolean isDigit(int c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
protected boolean isGenericDelimiter(int c) {
|
||||
return ':' == c || '/' == c || '?' == c || '#' == c || '[' == c || ']' == c || '@' == c;
|
||||
}
|
||||
|
||||
protected boolean isSubDelimiter(int c) {
|
||||
return '!' == c || '$' == c || '&' == c || '\'' == c || '(' == c || ')' == c || '*' == c || '+' == c ||
|
||||
',' == c || ';' == c || '=' == c;
|
||||
}
|
||||
|
||||
protected boolean isReserved(char c) {
|
||||
return isGenericDelimiter(c) || isReserved(c);
|
||||
}
|
||||
|
||||
protected boolean isUnreserved(int c) {
|
||||
return isAlpha(c) || isDigit(c) || '-' == c || '.' == c || '_' == c || '~' == c;
|
||||
}
|
||||
|
||||
protected boolean isPchar(int c) {
|
||||
return isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** The scheme URI component. */
|
||||
public static final UriComponent SCHEME_COMPONENT = new AbstractUriComponent() {
|
||||
public boolean isAllowed(int c) {
|
||||
return isAlpha(c) || isDigit(c) || '+' == c || '-' == c || '.' == c;
|
||||
}
|
||||
};
|
||||
|
||||
/** The authority URI component. */
|
||||
public static final UriComponent AUTHORITY_COMPONENT = new AbstractUriComponent() {
|
||||
public boolean isAllowed(int c) {
|
||||
return isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c;
|
||||
}
|
||||
};
|
||||
|
||||
/** The user info URI component. */
|
||||
public static final UriComponent USER_INFO_COMPONENT = new AbstractUriComponent() {
|
||||
public boolean isAllowed(int c) {
|
||||
return isUnreserved(c) || isSubDelimiter(c) || ':' == c;
|
||||
}
|
||||
};
|
||||
|
||||
/** The host URI component. */
|
||||
public static final UriComponent HOST_COMPONENT = new AbstractUriComponent() {
|
||||
public boolean isAllowed(int c) {
|
||||
return isUnreserved(c) || isSubDelimiter(c);
|
||||
}
|
||||
};
|
||||
|
||||
/** The port URI component. */
|
||||
public static final UriComponent PORT_COMPONENT = new AbstractUriComponent() {
|
||||
public boolean isAllowed(int c) {
|
||||
return isDigit(c);
|
||||
}
|
||||
};
|
||||
|
||||
/** The path URI component. */
|
||||
public static final UriComponent PATH_COMPONENT = new AbstractUriComponent() {
|
||||
public boolean isAllowed(int c) {
|
||||
return isPchar(c) || '/' == c;
|
||||
}
|
||||
};
|
||||
|
||||
/** The path segment URI component. */
|
||||
public static final UriComponent PATH_SEGMENT_COMPONENT = new AbstractUriComponent() {
|
||||
public boolean isAllowed(int c) {
|
||||
return isPchar(c);
|
||||
}
|
||||
};
|
||||
|
||||
/** The query URI component. */
|
||||
public static final UriComponent QUERY_COMPONENT = new AbstractUriComponent() {
|
||||
public boolean isAllowed(int c) {
|
||||
return isPchar(c) || '/' == c || '?' == c;
|
||||
}
|
||||
};
|
||||
|
||||
/** The query parameter URI component. */
|
||||
public static final UriComponent QUERY_PARAM_COMPONENT = new AbstractUriComponent() {
|
||||
public boolean isAllowed(int c) {
|
||||
if ('=' == c || '+' == c || '&' == c) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return isPchar(c) || '/' == c || '?' == c;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** The fragment URI component. */
|
||||
public static final UriComponent FRAGMENT_COMPONENT = new AbstractUriComponent() {
|
||||
public boolean isAllowed(int c) {
|
||||
return isPchar(c) || '/' == c || '?' == c;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2002-2011 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.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/** @author Arjen Poutsma */
|
||||
public class UriBuilderTests {
|
||||
|
||||
@Test
|
||||
public void plain() throws URISyntaxException {
|
||||
UriBuilder builder = UriBuilder.newInstance();
|
||||
URI result = builder.scheme("http").host("example.com").path("foo").queryParam("bar").fragment("baz").build();
|
||||
|
||||
URI expected = new URI("http://example.com/foo?bar#baz");
|
||||
assertEquals("Invalid result URI", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromUri() throws URISyntaxException {
|
||||
URI uri = new URI("http://example.com/foo?bar#baz");
|
||||
|
||||
URI result = UriBuilder.fromUri(uri).build();
|
||||
assertEquals("Invalid result URI", uri, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void templateVarsVarArgs() throws URISyntaxException {
|
||||
UriBuilder builder = UriBuilder.newInstance();
|
||||
URI result = builder.scheme("http").host("example.com").path("{foo}").build("bar");
|
||||
|
||||
URI expected = new URI("http://example.com/bar");
|
||||
assertEquals("Invalid result URI", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void templateVarsEncoded() throws URISyntaxException, UnsupportedEncodingException {
|
||||
URI result = UriBuilder.fromPath("{foo}").build("bar baz");
|
||||
|
||||
URI expected = new URI("/bar%20baz");
|
||||
assertEquals("Invalid result URI", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void templateVarsNotEncoded() throws URISyntaxException {
|
||||
UriBuilder builder = UriBuilder.newInstance();
|
||||
URI result = builder.scheme("http").host("example.com").path("{foo}").buildFromEncoded("bar%20baz");
|
||||
|
||||
URI expected = new URI("http://example.com/bar%20baz");
|
||||
assertEquals("Invalid result URI", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void templateVarsMap() throws URISyntaxException {
|
||||
Map<String, String> vars = Collections.singletonMap("foo", "bar");
|
||||
UriBuilder builder = UriBuilder.newInstance();
|
||||
URI result = builder.scheme("http").host("example.com").path("{foo}").build(vars);
|
||||
|
||||
URI expected = new URI("http://example.com/bar");
|
||||
assertEquals("Invalid result URI", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unusedTemplateVars() throws URISyntaxException {
|
||||
UriBuilder builder = UriBuilder.newInstance();
|
||||
URI result = builder.scheme("http").host("example.com").path("{foo}").build();
|
||||
|
||||
URI expected = new URI("http://example.com/%7Bfoo%7D");
|
||||
assertEquals("Invalid result URI", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathSegments() throws URISyntaxException {
|
||||
UriBuilder builder = UriBuilder.newInstance();
|
||||
URI result = builder.pathSegment("foo").pathSegment("bar").build();
|
||||
|
||||
URI expected = new URI("/foo/bar");
|
||||
assertEquals("Invalid result URI", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void queryParam() throws URISyntaxException {
|
||||
UriBuilder builder = UriBuilder.newInstance();
|
||||
URI result = builder.queryParam("baz", "qux", 42).build();
|
||||
|
||||
URI expected = new URI("?baz=qux&baz=42");
|
||||
assertEquals("Invalid result URI", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyQueryParam() throws URISyntaxException {
|
||||
UriBuilder builder = UriBuilder.newInstance();
|
||||
URI result = builder.queryParam("baz").build();
|
||||
|
||||
URI expected = new URI("?baz");
|
||||
assertEquals("Invalid result URI", expected, result);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2010 the original author or authors.
|
||||
* Copyright 2002-2011 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.
|
||||
@@ -23,9 +23,10 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
* @author Juergen Hoeller
|
||||
@@ -47,9 +48,9 @@ public class UriTemplateTests {
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void expandVarArgsInvalidAmountVariables() throws Exception {
|
||||
public void expandVarArgsNotEnoughVariables() throws Exception {
|
||||
UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}");
|
||||
template.expand("1", "42", "100");
|
||||
template.expand("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -110,6 +111,13 @@ public class UriTemplateTests {
|
||||
assertFalse("UriTemplate matches", template.matches(""));
|
||||
assertFalse("UriTemplate matches", template.matches(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchesCustomRegex() throws Exception {
|
||||
UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel:\\d+}");
|
||||
assertTrue("UriTemplate does not match", template.matches("http://example.com/hotels/42"));
|
||||
assertFalse("UriTemplate matches", template.matches("http://example.com/hotels/foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void match() throws Exception {
|
||||
@@ -122,6 +130,17 @@ public class UriTemplateTests {
|
||||
assertEquals("Invalid match", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchCustomRegex() throws Exception {
|
||||
Map<String, String> expected = new HashMap<String, String>(2);
|
||||
expected.put("booking", "42");
|
||||
expected.put("hotel", "1");
|
||||
|
||||
UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel:\\d}/bookings/{booking:\\d+}");
|
||||
Map<String, String> result = template.match("http://example.com/hotels/1/bookings/42");
|
||||
assertEquals("Invalid match", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchDuplicate() throws Exception {
|
||||
UriTemplate template = new UriTemplate("/order/{c}/{c}/{c}");
|
||||
|
||||
Reference in New Issue
Block a user