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.internal.StringUtils.*; 018 019import java.io.*; 020import java.net.*; 021 022/** 023 * Class used to create absolute and root-relative URIs based on your current URI 'location' and rules about how to 024 * make such resolutions. 025 * 026 * <p> 027 * Combines a {@link UriContext} instance with rules for resolution ({@link UriResolution} and relativity 028 * ({@link UriRelativity}) to define simple {@link #resolve(Object)} and {@link #append(Appendable, Object)} methods. 029 * 030 * <p> 031 * Three special protocols are used to represent context-root-relative, servlet-relative, and request-path-relative 032 * URIs: 033 * <js>"context:/"</js>, <js>"servlet:/"</js>, and <js>"request:/"</js>. 034 * 035 * <p> 036 * The following list shows the protocols of URLs that can be resolved with this class: 037 * <ul> 038 * <li><js>"foo://foo"</js> - Absolute URI. 039 * <li><js>"/foo"</js> - Root-relative URI. 040 * <li><js>"/"</js> - Root URI. 041 * <li><js>"context:/foo"</js> - Context-root-relative URI. 042 * <li><js>"context:/"</js> - Context-root URI. 043 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 044 * <li><js>"servlet:/"</js> - Servlet-path URI. 045 * <li><js>"request:/foo"</js> - Request-path-relative URI. 046 * <li><js>"request:/"</js> - Request-path URI. 047 * <li><js>"foo"</js> - Path-info-relative URI. 048 * <li><js>""</js> - Path-info URI. 049 * </ul> 050*/ 051public class UriResolver { 052 053 private final UriResolution resolution; 054 private final UriRelativity relativity; 055 private final String authority, contextRoot, servletPath, pathInfo, parentPath; 056 057 /** 058 * Constructor. 059 * 060 * @param resolution Rule on how URIs should be resolved. 061 * @param relativity Rule on what relative URIs are relative to. 062 * @param uriContext Current URI context (i.e. the current URI 'location'). 063 */ 064 public UriResolver(UriResolution resolution, UriRelativity relativity, UriContext uriContext) { 065 this.resolution = resolution; 066 this.relativity = relativity; 067 this.authority = uriContext.authority; 068 this.contextRoot = uriContext.contextRoot; 069 this.servletPath = uriContext.servletPath; 070 this.pathInfo = uriContext.pathInfo; 071 this.parentPath = uriContext.parentPath; 072 } 073 074 /** 075 * Converts the specified URI to absolute form based on values in this context. 076 * 077 * @param uri 078 * The URI to convert to absolute form. 079 * Can be any of the following: 080 * <ul> 081 * <li>{@link java.net.URI} 082 * <li>{@link java.net.URL} 083 * <li>{@link CharSequence} 084 * </ul> 085 * URI can be any of the following forms: 086 * <ul> 087 * <li><js>"foo://foo"</js> - Absolute URI. 088 * <li><js>"/foo"</js> - Root-relative URI. 089 * <li><js>"/"</js> - Root URI. 090 * <li><js>"context:/foo"</js> - Context-root-relative URI. 091 * <li><js>"context:/"</js> - Context-root URI. 092 * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. 093 * <li><js>"servlet:/"</js> - Servlet-path URI. 094 * <li><js>"request:/foo"</js> - Request-path-relative URI. 095 * <li><js>"request:/"</js> - Request-path URI. 096 * <li><js>"foo"</js> - Path-info-relative URI. 097 * <li><js>""</js> - Path-info URI. 098 * </ul> 099 * @return The converted URI. 100 */ 101 public String resolve(Object uri) { 102 return resolve(uri, resolution); 103 } 104 105 private String resolve(Object uri, UriResolution res) { 106 String s = asString(uri); 107 if (isAbsoluteUri(s)) 108 return hasDotSegments(s) && res != NONE ? normalize(s) : s; 109 if (res == ROOT_RELATIVE && startsWith(s, '/')) 110 return hasDotSegments(s) ? normalize(s) : s; 111 if (res == NONE && ! isSpecialUri(s)) 112 return s; 113 return append(new StringBuilder(), s).toString(); 114 } 115 116 /** 117 * Relativizes a URI. 118 * 119 * <p> 120 * Similar to {@link URI#relativize(URI)}, except supports special protocols (e.g. <js>"servlet:/"</js>) for both 121 * the <code>relativeTo</code> and <code>uri</code> parameters. 122 * 123 * <p> 124 * For example, to relativize a URI to its servlet-relative form: 125 * <p class='bcode'> 126 * <jc>// relativeUri == "path/foo"</jc> 127 * String relativeUri = resolver.relativize(<js>"servlet:/"</js>, <js>"/context/servlet/path/foo"</js>); 128 * </p> 129 * 130 * @param relativeTo The URI to relativize against. 131 * @param uri The URI to relativize. 132 * @return The relativized URI. 133 */ 134 public String relativize(Object relativeTo, Object uri) { 135 String r = resolve(relativeTo, ABSOLUTE); 136 String s = resolve(uri, ABSOLUTE); 137 return URI.create(r).relativize(URI.create(s)).toString(); 138 } 139 140 /** 141 * Same as {@link #resolve(Object)} except appends result to the specified appendable. 142 * 143 * @param a The appendable to append the URL to. 144 * @param o The URI to convert to absolute form. 145 * @return The same appendable passed in. 146 */ 147 public Appendable append(Appendable a, Object o) { 148 149 try { 150 String uri = asString(o); 151 uri = nullIfEmpty(uri); 152 boolean needsNormalize = hasDotSegments(uri) && resolution != null; 153 154 // Absolute paths are not changed. 155 if (isAbsoluteUri(uri)) 156 return a.append(needsNormalize ? normalize(uri) : uri); 157 if (resolution == NONE && ! isSpecialUri(uri)) 158 return a.append(emptyIfNull(uri)); 159 if (resolution == ROOT_RELATIVE && startsWith(uri, '/')) 160 return a.append(needsNormalize ? normalize(uri) : uri); 161 162 Appendable a2 = needsNormalize ? new StringBuilder() : a; 163 164 // Root-relative path 165 if (startsWith(uri, '/')) { 166 if (authority != null) 167 a2.append(authority); 168 if (uri.length() != 1) 169 a2.append(uri); 170 else if (authority == null) 171 a2.append('/'); 172 } 173 174 // Context-relative path 175 else if (uri != null && uri.startsWith("context:/")) { 176 if (resolution == ABSOLUTE && authority != null) 177 a2.append(authority); 178 if (contextRoot != null) 179 a2.append('/').append(contextRoot); 180 if (uri.length() > 9) 181 a2.append('/').append(uri.substring(9)); 182 else if (contextRoot == null && (authority == null || resolution != ABSOLUTE)) 183 a2.append('/'); 184 } 185 186 // Resource-relative path 187 else if (uri != null && uri.startsWith("servlet:/")) { 188 if (resolution == ABSOLUTE && authority != null) 189 a2.append(authority); 190 if (contextRoot != null) 191 a2.append('/').append(contextRoot); 192 if (servletPath != null) 193 a2.append('/').append(servletPath); 194 if (uri.length() > 9) 195 a2.append('/').append(uri.substring(9)); 196 else if (servletPath == null && contextRoot == null && (authority == null || resolution != ABSOLUTE)) 197 a2.append('/'); 198 } 199 200 // Request-relative path 201 else if (uri != null && uri.startsWith("request:/")) { 202 if (resolution == ABSOLUTE && authority != null) 203 a2.append(authority); 204 if (contextRoot != null) 205 a2.append('/').append(contextRoot); 206 if (servletPath != null) 207 a2.append('/').append(servletPath); 208 if (pathInfo != null) 209 a2.append('/').append(pathInfo); 210 if (uri.length() > 9) 211 a2.append('/').append(uri.substring(9)); 212 else if (servletPath == null && contextRoot == null && pathInfo == null && (authority == null || resolution != ABSOLUTE)) 213 a2.append('/'); 214 } 215 216 // Relative path 217 else { 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 (relativity == RESOURCE && uri != null) 225 a2.append('/').append(uri); 226 else if (relativity == PATH_INFO) { 227 if (uri == null) { 228 if (pathInfo != null) 229 a2.append('/').append(pathInfo); 230 } else { 231 if (parentPath != null) 232 a2.append('/').append(parentPath); 233 a2.append('/').append(uri); 234 } 235 } 236 else if (uri == null && contextRoot == null && servletPath == null && (authority == null || resolution != ABSOLUTE)) 237 a2.append('/'); 238 } 239 240 if (needsNormalize) 241 a.append(normalize(a2.toString())); 242 243 return a; 244 } catch (IOException e) { 245 throw new RuntimeException(e); 246 } 247 } 248 249 private static boolean isSpecialUri(String s) { 250 if (s == null || s.length() == 0) 251 return false; 252 char c = s.charAt(0); 253 if (c != 's' && c != 'c' && c != 'r') 254 return false; 255 return s.startsWith("servlet:/") || s.startsWith("context:/") || s.startsWith("request:/"); 256 } 257 258 private static String normalize(String s) { 259 s = URI.create(s).normalize().toString(); 260 if (s.length() > 1 && s.charAt(s.length()-1) == '/') 261 s = s.substring(0, s.length()-1); 262 return s; 263 } 264 265 private static boolean hasDotSegments(String s) { 266 if (s == null) 267 return false; 268 for (int i = 0; i < s.length()-1; i++) { 269 char c = s.charAt(i); 270 if (i == 0 && c == '/') 271 return true; 272 if (c == '/' && s.charAt(i+1) == '.') 273 return true; 274 } 275 return false; 276 } 277}