001/* 002 * Copyright 2002-2016 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.io.UnsupportedEncodingException; 020import java.net.URI; 021import java.net.URISyntaxException; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026/** 027 * Default implementation of {@link UriTemplateHandler} based on the use of 028 * {@link UriComponentsBuilder} for expanding and encoding variables. 029 * 030 * <p>There are also several properties to customize how URI template handling 031 * is performed, including a {@link #setBaseUrl baseUrl} to be used as a prefix 032 * for all URI templates and a couple of encoding related options — 033 * {@link #setParsePath parsePath} and {@link #setStrictEncoding strictEncoding} 034 * respectively. 035 * 036 * @author Rossen Stoyanchev 037 * @since 4.2 038 */ 039public class DefaultUriTemplateHandler extends AbstractUriTemplateHandler { 040 041 private boolean parsePath; 042 043 private boolean strictEncoding; 044 045 046 /** 047 * Whether to parse the path of a URI template string into path segments. 048 * <p>If set to {@code true} the URI template path is immediately decomposed 049 * into path segments any URI variables expanded into it are then subject to 050 * path segment encoding rules. In effect URI variables in the path have any 051 * "/" characters percent encoded. 052 * <p>By default this is set to {@code false} in which case the path is kept 053 * as a full path and expanded URI variables will preserve "/" characters. 054 * @param parsePath whether to parse the path into path segments 055 */ 056 public void setParsePath(boolean parsePath) { 057 this.parsePath = parsePath; 058 } 059 060 /** 061 * Whether the handler is configured to parse the path into path segments. 062 */ 063 public boolean shouldParsePath() { 064 return this.parsePath; 065 } 066 067 /** 068 * Whether to encode characters outside the unreserved set as defined in 069 * <a href="https://tools.ietf.org/html/rfc3986#section-2">RFC 3986 Section 2</a>. 070 * This ensures a URI variable value will not contain any characters with a 071 * reserved purpose. 072 * <p>By default this is set to {@code false} in which case only characters 073 * illegal for the given URI component are encoded. For example when expanding 074 * a URI variable into a path segment the "/" character is illegal and 075 * encoded. The ";" character however is legal and not encoded even though 076 * it has a reserved purpose. 077 * <p><strong>Note:</strong> this property supersedes the need to also set 078 * the {@link #setParsePath parsePath} property. 079 * @param strictEncoding whether to perform strict encoding 080 * @since 4.3 081 */ 082 public void setStrictEncoding(boolean strictEncoding) { 083 this.strictEncoding = strictEncoding; 084 } 085 086 /** 087 * Whether to strictly encode any character outside the unreserved set. 088 */ 089 public boolean isStrictEncoding() { 090 return this.strictEncoding; 091 } 092 093 094 @Override 095 protected URI expandInternal(String uriTemplate, Map<String, ?> uriVariables) { 096 UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate); 097 UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables); 098 return createUri(uriComponents); 099 } 100 101 @Override 102 protected URI expandInternal(String uriTemplate, Object... uriVariables) { 103 UriComponentsBuilder uriComponentsBuilder = initUriComponentsBuilder(uriTemplate); 104 UriComponents uriComponents = expandAndEncode(uriComponentsBuilder, uriVariables); 105 return createUri(uriComponents); 106 } 107 108 /** 109 * Create a {@code UriComponentsBuilder} from the URI template string. 110 * This implementation also breaks up the path into path segments depending 111 * on whether {@link #setParsePath parsePath} is enabled. 112 */ 113 protected UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) { 114 UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(uriTemplate); 115 if (shouldParsePath() && !isStrictEncoding()) { 116 List<String> pathSegments = builder.build().getPathSegments(); 117 builder.replacePath(null); 118 for (String pathSegment : pathSegments) { 119 builder.pathSegment(pathSegment); 120 } 121 } 122 return builder; 123 } 124 125 protected UriComponents expandAndEncode(UriComponentsBuilder builder, Map<String, ?> uriVariables) { 126 if (!isStrictEncoding()) { 127 return builder.buildAndExpand(uriVariables).encode(); 128 } 129 else { 130 Map<String, Object> encodedUriVars = new HashMap<String, Object>(uriVariables.size()); 131 for (Map.Entry<String, ?> entry : uriVariables.entrySet()) { 132 encodedUriVars.put(entry.getKey(), applyStrictEncoding(entry.getValue())); 133 } 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 = new Object[uriVariables.length]; 144 for (int i = 0; i < uriVariables.length; i++) { 145 encodedUriVars[i] = applyStrictEncoding(uriVariables[i]); 146 } 147 return builder.buildAndExpand(encodedUriVars); 148 } 149 } 150 151 private String applyStrictEncoding(Object value) { 152 String stringValue = (value != null ? value.toString() : ""); 153 try { 154 return UriUtils.encode(stringValue, "UTF-8"); 155 } 156 catch (UnsupportedEncodingException ex) { 157 // Should never happen 158 throw new IllegalStateException("Failed to encode URI variable", ex); 159 } 160 } 161 162 private URI createUri(UriComponents uriComponents) { 163 try { 164 // Avoid further encoding (in the case of strictEncoding=true) 165 return new URI(uriComponents.toUriString()); 166 } 167 catch (URISyntaxException ex) { 168 throw new IllegalStateException("Could not create URI object: " + ex.getMessage(), ex); 169 } 170 } 171 172}