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