001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau; 014 015import static org.apache.juneau.UriRelativity.*; 016import static org.apache.juneau.UriResolution.*; 017import static org.apache.juneau.common.internal.StringUtils.*; 018import static org.apache.juneau.common.internal.ThrowableUtils.*; 019 020import java.io.*; 021import java.net.*; 022 023/** 024 * Class used to create absolute and root-relative URIs based on your current URI 'location' and rules about how to 025 * make such resolutions. 026 * 027 * <p> 028 * Combines a {@link UriContext} instance with rules for resolution ({@link UriResolution} and relativity 029 * ({@link UriRelativity}) to define simple {@link #resolve(Object)} and {@link #append(Appendable, Object)} methods. 030 * 031 * <p> 032 * Three special protocols are used to represent context-root-relative, servlet-relative, and request-path-relative 033 * URIs: 034 * <js>"context:/"</js>, <js>"servlet:/"</js>, and <js>"request:/"</js>. 035 * 036 * <p> 037 * The following list shows the protocols of URLs that can be resolved with this class: 038 * <ul> 039 * <li><js>"foo://foo"</js> - Absolute URI. 040 * <li><js>"/foo"</js> - Root-relative URI. 041 * <li><js>"/"</js> - Root URI. 042 * <li><js>"context:/foo"</js> - Context-root-relative URI. 043 * <li><js>"context:/"</js> - Context-root URI. 044 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 045 * <li><js>"servlet:/"</js> - Servlet-path URI. 046 * <li><js>"request:/foo"</js> - Request-path-relative URI. 047 * <li><js>"request:/"</js> - Request-path URI. 048 * <li><js>"foo"</js> - Path-info-relative URI. 049 * <li><js>""</js> - Path-info URI. 050 * </ul> 051 * 052 * <h5 class='section'>See Also:</h5><ul> 053 * </ul> 054 */ 055public class UriResolver { 056 057 private final UriResolution resolution; 058 private final UriRelativity relativity; 059 private final String authority, contextRoot, servletPath, pathInfo, parentPath; 060 061 /** 062 * Static creator. 063 * 064 * @param resolution Rule on how URIs should be resolved. 065 * @param relativity Rule on what relative URIs are relative to. 066 * @param uriContext Current URI context (i.e. the current URI 'location'). 067 * @return A new {@link UriResolver} object. 068 */ 069 public static UriResolver of(UriResolution resolution, UriRelativity relativity, UriContext uriContext) { 070 return new UriResolver(resolution, relativity, uriContext); 071 } 072 073 /** 074 * Constructor. 075 * 076 * @param resolution Rule on how URIs should be resolved. 077 * @param relativity Rule on what relative URIs are relative to. 078 * @param uriContext Current URI context (i.e. the current URI 'location'). 079 */ 080 public UriResolver(UriResolution resolution, UriRelativity relativity, UriContext uriContext) { 081 this.resolution = resolution; 082 this.relativity = relativity; 083 this.authority = uriContext.authority; 084 this.contextRoot = uriContext.contextRoot; 085 this.servletPath = uriContext.servletPath; 086 this.pathInfo = uriContext.pathInfo; 087 this.parentPath = uriContext.parentPath; 088 } 089 090 /** 091 * Converts the specified URI to absolute form based on values in this context. 092 * 093 * @param uri 094 * The URI to convert to absolute form. 095 * Can be any of the following: 096 * <ul> 097 * <li>{@link java.net.URI} 098 * <li>{@link java.net.URL} 099 * <li>{@link CharSequence} 100 * </ul> 101 * URI can be any of the following forms: 102 * <ul> 103 * <li><js>"foo://foo"</js> - Absolute URI. 104 * <li><js>"/foo"</js> - Root-relative URI. 105 * <li><js>"/"</js> - Root URI. 106 * <li><js>"context:/foo"</js> - Context-root-relative URI. 107 * <li><js>"context:/"</js> - Context-root URI. 108 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 109 * <li><js>"servlet:/"</js> - Servlet-path URI. 110 * <li><js>"request:/foo"</js> - Request-path-relative URI. 111 * <li><js>"request:/"</js> - Request-path URI. 112 * <li><js>"foo"</js> - Path-info-relative URI. 113 * <li><js>""</js> - Path-info URI. 114 * </ul> 115 * @return The converted URI. 116 */ 117 public String resolve(Object uri) { 118 return resolve(uri, resolution); 119 } 120 121 private String resolve(Object uri, UriResolution res) { 122 String s = stringify(uri); 123 if (isAbsoluteUri(s)) 124 return hasDotSegments(s) && res != NONE ? normalize(s) : s; 125 if (res == ROOT_RELATIVE && startsWith(s, '/')) 126 return hasDotSegments(s) ? normalize(s) : s; 127 if (res == NONE && ! isSpecialUri(s)) 128 return s; 129 return append(new StringBuilder(), s).toString(); 130 } 131 132 /** 133 * Relativizes a URI. 134 * 135 * <p> 136 * Similar to {@link URI#relativize(URI)}, except supports special protocols (e.g. <js>"servlet:/"</js>) for both 137 * the <c>relativeTo</c> and <c>uri</c> parameters. 138 * 139 * <p> 140 * For example, to relativize a URI to its servlet-relative form: 141 * <p class='bjava'> 142 * <jc>// relativeUri == "path/foo"</jc> 143 * String <jv>relativeUri</jv> = <jv>resolver</jv>.relativize(<js>"servlet:/"</js>, <js>"/context/servlet/path/foo"</js>); 144 * </p> 145 * 146 * @param relativeTo The URI to relativize against. 147 * @param uri The URI to relativize. 148 * @return The relativized URI. 149 */ 150 public String relativize(Object relativeTo, Object uri) { 151 String r = resolve(relativeTo, ABSOLUTE); 152 String s = resolve(uri, ABSOLUTE); 153 return URI.create(r).relativize(URI.create(s)).toString(); 154 } 155 156 /** 157 * Same as {@link #resolve(Object)} except appends result to the specified appendable. 158 * 159 * @param a The appendable to append the URL to. 160 * @param o The URI to convert to absolute form. 161 * @return The same appendable passed in. 162 */ 163 public Appendable append(Appendable a, Object o) { 164 165 try { 166 String uri = stringify(o); 167 uri = nullIfEmpty(uri); 168 boolean needsNormalize = hasDotSegments(uri) && resolution != null; 169 170 // Absolute paths are not changed. 171 if (isAbsoluteUri(uri)) 172 return a.append(needsNormalize ? normalize(uri) : uri); 173 if (resolution == NONE && ! isSpecialUri(uri)) 174 return a.append(emptyIfNull(uri)); 175 if (resolution == ROOT_RELATIVE && startsWith(uri, '/')) 176 return a.append(needsNormalize ? normalize(uri) : uri); 177 178 Appendable a2 = needsNormalize ? new StringBuilder() : a; 179 180 // Root-relative path 181 if (startsWith(uri, '/')) { 182 if (authority != null) 183 a2.append(authority); 184 if (uri.length() != 1) 185 a2.append(uri); 186 else if (authority == null) 187 a2.append('/'); 188 } 189 190 // Context-relative path 191 else if (uri != null && uri.startsWith("context:/")) { 192 if (resolution == ABSOLUTE && authority != null) 193 a2.append(authority); 194 if (contextRoot != null) 195 a2.append('/').append(contextRoot); 196 if (uri.length() > 9) 197 a2.append('/').append(uri.substring(9)); 198 else if (contextRoot == null && (authority == null || resolution != ABSOLUTE)) 199 a2.append('/'); 200 } 201 202 // Resource-relative path 203 else if (uri != null && uri.startsWith("servlet:/")) { 204 if (resolution == ABSOLUTE && authority != null) 205 a2.append(authority); 206 if (contextRoot != null) 207 a2.append('/').append(contextRoot); 208 if (servletPath != null) 209 a2.append('/').append(servletPath); 210 if (uri.length() > 9) 211 a2.append('/').append(uri.substring(9)); 212 else if (servletPath == null && contextRoot == null && (authority == null || resolution != ABSOLUTE)) 213 a2.append('/'); 214 } 215 216 // Request-relative path 217 else if (uri != null && uri.startsWith("request:/")) { 218 if (resolution == ABSOLUTE && authority != null) 219 a2.append(authority); 220 if (contextRoot != null) 221 a2.append('/').append(contextRoot); 222 if (servletPath != null) 223 a2.append('/').append(servletPath); 224 if (pathInfo != null) 225 a2.append('/').append(pathInfo); 226 if (uri.length() > 9) 227 a2.append('/').append(uri.substring(9)); 228 else if (servletPath == null && contextRoot == null && pathInfo == null && (authority == null || resolution != ABSOLUTE)) 229 a2.append('/'); 230 } 231 232 // Relative path 233 else { 234 if (resolution == ABSOLUTE && authority != null) 235 a2.append(authority); 236 if (contextRoot != null) 237 a2.append('/').append(contextRoot); 238 if (servletPath != null) 239 a2.append('/').append(servletPath); 240 if (relativity == RESOURCE && uri != null) 241 a2.append('/').append(uri); 242 else if (relativity == PATH_INFO) { 243 if (uri == null) { 244 if (pathInfo != null) 245 a2.append('/').append(pathInfo); 246 } else { 247 if (parentPath != null) 248 a2.append('/').append(parentPath); 249 a2.append('/').append(uri); 250 } 251 } 252 else if (uri == null && contextRoot == null && servletPath == null && (authority == null || resolution != ABSOLUTE)) 253 a2.append('/'); 254 } 255 256 if (needsNormalize) 257 a.append(normalize(a2.toString())); 258 259 return a; 260 } catch (IOException e) { 261 throw asRuntimeException(e); 262 } 263 } 264 265 private static boolean isSpecialUri(String s) { 266 if (s == null || s.isEmpty()) 267 return false; 268 char c = s.charAt(0); 269 if (c != 's' && c != 'c' && c != 'r') 270 return false; 271 return s.startsWith("servlet:/") || s.startsWith("context:/") || s.startsWith("request:/"); 272 } 273 274 private static String normalize(String s) { 275 s = URI.create(s).normalize().toString(); 276 if (s.length() > 1 && s.charAt(s.length()-1) == '/') 277 s = s.substring(0, s.length()-1); 278 return s; 279 } 280 281 private static boolean hasDotSegments(String s) { 282 if (s == null) 283 return false; 284 for (int i = 0; i < s.length()-1; i++) { 285 char c = s.charAt(i); 286 if ((i == 0 && c == '/') || (c == '/' && s.charAt(i+1) == '.')) 287 return true; 288 } 289 return false; 290 } 291}