001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.util;
018
019import java.net.URI;
020import java.net.URISyntaxException;
021import java.util.List;
022import java.util.Map;
023
024/**
025 * Default implementation of {@link UriTemplateHandler} based on the use of
026 * {@link UriComponentsBuilder} for expanding and encoding variables.
027 *
028 * <p>There are also several properties to customize how URI template handling
029 * is performed, including a {@link #setBaseUrl baseUrl} to be used as a prefix
030 * for all URI templates and a couple of encoding related options &mdash;
031 * {@link #setParsePath parsePath} and {@link #setStrictEncoding strictEncoding}
032 * respectively.
033 *
034 * @author Rossen Stoyanchev
035 * @since 4.2
036 * @deprecated as of 5.0 in favor of {@link DefaultUriBuilderFactory}.
037 * <p><strong>Note:</strong> {@link DefaultUriBuilderFactory} has a different
038 * default for the {@link #setParsePath(boolean) parsePath} property (from
039 * false to true).
040 */
041@Deprecated
042public class DefaultUriTemplateHandler extends AbstractUriTemplateHandler {
043
044        private boolean parsePath;
045
046        private boolean strictEncoding;
047
048
049        /**
050         * Whether to parse the path of a URI template string into path segments.
051         * <p>If set to {@code true} the URI template path is immediately decomposed
052         * into path segments any URI variables expanded into it are then subject to
053         * path segment encoding rules. In effect URI variables in the path have any
054         * "/" characters percent encoded.
055         * <p>By default this is set to {@code false} in which case the path is kept
056         * as a full path and expanded URI variables will preserve "/" characters.
057         * @param parsePath whether to parse the path into path segments
058         */
059        public void setParsePath(boolean parsePath) {
060                this.parsePath = parsePath;
061        }
062
063        /**
064         * Whether the handler is configured to parse the path into path segments.
065         */
066        public boolean shouldParsePath() {
067                return this.parsePath;
068        }
069
070        /**
071         * Whether to encode characters outside the unreserved set as defined in
072         * <a href="https://tools.ietf.org/html/rfc3986#section-2">RFC 3986 Section 2</a>.
073         * This ensures a URI variable value will not contain any characters with a
074         * reserved purpose.
075         * <p>By default this is set to {@code false} in which case only characters
076         * illegal for the given URI component are encoded. For example when expanding
077         * a URI variable into a path segment the "/" character is illegal and
078         * encoded. The ";" character however is legal and not encoded even though
079         * it has a reserved purpose.
080         * <p><strong>Note:</strong> this property supersedes the need to also set
081         * the {@link #setParsePath parsePath} property.
082         * @param strictEncoding whether to perform strict encoding
083         * @since 4.3
084         */
085        public void setStrictEncoding(boolean strictEncoding) {
086                this.strictEncoding = strictEncoding;
087        }
088
089        /**
090         * Whether to strictly encode any character outside the unreserved set.
091         */
092        public boolean isStrictEncoding() {
093                return this.strictEncoding;
094        }
095
096
097        @Override
098        protected URI expandInternal(String uriTemplate, Map<String, ?> uriVariables) {
099                UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate);
100                UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables);
101                return createUri(uriComponents);
102        }
103
104        @Override
105        protected URI expandInternal(String uriTemplate, Object... uriVariables) {
106                UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate);
107                UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables);
108                return createUri(uriComponents);
109        }
110
111        /**
112         * Create a {@code UriComponentsBuilder} from the URI template string.
113         * This implementation also breaks up the path into path segments depending
114         * on whether {@link #setParsePath parsePath} is enabled.
115         */
116        protected UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
117                UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uriTemplate);
118                if (shouldParsePath() && !isStrictEncoding()) {
119                        List<String> pathSegments = builder.build().getPathSegments();
120                        builder.replacePath(null);
121                        for (String pathSegment : pathSegments) {
122                                builder.pathSegment(pathSegment);
123                        }
124                }
125                return builder;
126        }
127
128        protected UriComponents expandAndEncode(UriComponentsBuilder builder, Map<String, ?> uriVariables) {
129                if (!isStrictEncoding()) {
130                        return builder.buildAndExpand(uriVariables).encode();
131                }
132                else {
133                        Map<String, ?> encodedUriVars = UriUtils.encodeUriVariables(uriVariables);
134                        return builder.buildAndExpand(encodedUriVars);
135                }
136        }
137
138        protected UriComponents expandAndEncode(UriComponentsBuilder builder, Object[] uriVariables) {
139                if (!isStrictEncoding()) {
140                        return builder.buildAndExpand(uriVariables).encode();
141                }
142                else {
143                        Object[] encodedUriVars = UriUtils.encodeUriVariables(uriVariables);
144                        return builder.buildAndExpand(encodedUriVars);
145                }
146        }
147
148        private URI createUri(UriComponents uriComponents) {
149                try {
150                        // Avoid further encoding (in the case of strictEncoding=true)
151                        return new URI(uriComponents.toUriString());
152                }
153                catch (URISyntaxException ex) {
154                        throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex);
155                }
156        }
157
158}