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.commons.utils.CollectionUtils.*; 020import static org.apache.juneau.commons.utils.StringUtils.*; 021import static org.apache.juneau.commons.utils.ThrowableUtils.*; 022import static org.apache.juneau.commons.utils.Utils.*; 023 024import java.util.function.Supplier; 025 026import org.apache.juneau.annotation.*; 027import org.apache.juneau.collections.*; 028import org.apache.juneau.commons.collections.*; 029import org.apache.juneau.parser.*; 030 031/** 032 * Represents a URL broken into authority/context-root/servlet-path/path-info parts. 033 * 034 * <p> 035 * A typical request against a URL takes the following form: 036 * <p class='bcode'> 037 * http://host:port/context-root/servlet-path/path-info 038 * | authority | context | resource | path | 039 * +--------------------------------------------------+ 040 * </p> 041 * 042 * <p> 043 * This class allows you to convert URL strings to absolute (e.g. <js>"http://host:port/foo/bar"</js>) or root-relative 044 * (e.g. <js>"/foo/bar"</js>) URLs. 045 * 046 */ 047@Bean 048public class UriContext { 049 050 /** 051 * Default URI context. 052 * 053 * <p> 054 * No information about authority, servlet-root, context-root, or path-info is known. 055 */ 056 public static final UriContext DEFAULT = new UriContext(); 057 058 /** 059 * Static creator. 060 * 061 * @param s 062 * The input string. 063 * <br>Example: <js>{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}</js> 064 * @return A new {@link UriContext} object. 065 */ 066 public static UriContext of(String s) { 067 try { 068 return new UriContext(s); 069 } catch (ParseException e) { 070 throw toRex(e); 071 } 072 } 073 074 /** 075 * Static creator. 076 * 077 * @param authority 078 * The authority portion of URL (e.g. <js>"http://hostname:port"</js>) 079 * @param contextRoot 080 * The context root of the application (e.g. <js>"/context-root"</js>, or <js>"context-root"</js>) 081 * @param servletPath 082 * The servlet path (e.g. <js>"/servlet-path"</js>, or <js>"servlet-path"</js>) 083 * @param pathInfo 084 * The path info (e.g. <js>"/path-info"</js>, or <js>"path-info"</js>) 085 * @return A new {@link UriContext} object. 086 */ 087 public static UriContext of(String authority, String contextRoot, String servletPath, String pathInfo) { 088 return new UriContext(authority, contextRoot, servletPath, pathInfo); 089 } 090 091 private static String getParent(String uri) { 092 var i = uri.lastIndexOf('/'); 093 if (i <= 1) 094 return "/"; 095 return uri.substring(0, i); 096 } 097 098 @SuppressWarnings("javadoc") 099 public final String authority, contextRoot, servletPath, pathInfo, parentPath; 100 101 // Memoized suppliers. 102 private final Supplier<String> aContextRoot, rContextRoot, aServletPath, rResource, aPathInfo, rPath; 103 104 /** 105 * Default constructor. 106 * 107 * <p> 108 * All <jk>null</jk> values. 109 */ 110 public UriContext() { 111 this(null, null, null, null); 112 } 113 114 /** 115 * String constructor. 116 * 117 * <p> 118 * Input string is a JSON object with the following format: 119 * <js>{authority:'xxx',contextRoot:'xxx',servletPath:'xxx',pathInfo:'xxx'}</js> 120 * 121 * @param s 122 * The input string. 123 * <br>Example: <js>{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}</js> 124 * @throws ParseException 125 * If input string is not a valid JSON object. 126 */ 127 public UriContext(String s) throws ParseException { 128 var m = JsonMap.ofJson(s); 129 this.authority = nullIfEmpty(trimSlashes(m.getString("authority"))); 130 this.contextRoot = nullIfEmpty(trimSlashes(m.getString("contextRoot"))); 131 this.servletPath = nullIfEmpty(trimSlashes(m.getString("servletPath"))); 132 this.pathInfo = nullIfEmpty(trimSlashes(m.getString("pathInfo"))); 133 this.parentPath = this.pathInfo == null || this.pathInfo.indexOf('/') == -1 ? null : this.pathInfo.substring(0, this.pathInfo.lastIndexOf('/')); 134 this.rContextRoot = mem(() -> findRContextRoot()); 135 this.rResource = mem(() -> findRResource()); 136 this.rPath = mem(() -> findRPath()); 137 this.aContextRoot = mem(() -> findAContextRoot()); 138 this.aServletPath = mem(() -> findAServletPath()); 139 this.aPathInfo = mem(() -> findAPathInfo()); 140 } 141 142 /** 143 * Constructor. 144 * 145 * <p> 146 * Leading and trailing slashes are trimmed of all parameters. 147 * 148 * <p> 149 * Any parameter can be <jk>null</jk>. Blanks and nulls are equivalent. 150 * 151 * @param authority 152 * The authority portion of URL (e.g. <js>"http://hostname:port"</js>) 153 * @param contextRoot 154 * The context root of the application (e.g. <js>"/context-root"</js>, or <js>"context-root"</js>) 155 * @param servletPath 156 * The servlet path (e.g. <js>"/servlet-path"</js>, or <js>"servlet-path"</js>) 157 * @param pathInfo 158 * The path info (e.g. <js>"/path-info"</js>, or <js>"path-info"</js>) 159 */ 160 @Beanc 161 public UriContext(@Name("authority") String authority, @Name("contextRoot") String contextRoot, @Name("servletPath") String servletPath, @Name("pathInfo") String pathInfo) { 162 this.authority = nullIfEmpty(trimSlashes(authority)); 163 this.contextRoot = nullIfEmpty(trimSlashes(contextRoot)); 164 this.servletPath = nullIfEmpty(trimSlashes(servletPath)); 165 this.pathInfo = nullIfEmpty(trimSlashes(pathInfo)); 166 this.parentPath = this.pathInfo == null || this.pathInfo.indexOf('/') == -1 ? null : this.pathInfo.substring(0, this.pathInfo.lastIndexOf('/')); 167 this.rContextRoot = mem(() -> findRContextRoot()); 168 this.rResource = mem(() -> findRResource()); 169 this.rPath = mem(() -> findRPath()); 170 this.aContextRoot = mem(() -> findAContextRoot()); 171 this.aServletPath = mem(() -> findAServletPath()); 172 this.aPathInfo = mem(() -> findAPathInfo()); 173 } 174 175 private String findRContextRoot() { 176 return contextRoot == null ? "/" : ('/' + contextRoot); 177 } 178 179 private String findRResource() { 180 // @formatter:off 181 if (contextRoot == null) 182 return ( 183 servletPath == null 184 ? "/" 185 : ('/' + servletPath) 186 ); 187 return ( 188 servletPath == null 189 ? ('/' + contextRoot) 190 : ('/' + contextRoot + '/' + servletPath) 191 ); 192 // @formatter:on 193 } 194 195 private String findRPath() { 196 // @formatter:off 197 if (contextRoot == null) { 198 if (servletPath == null) 199 return ( 200 pathInfo == null 201 ? "/" 202 : ('/' + pathInfo) 203 ); 204 return ( 205 pathInfo == null 206 ? ('/' + servletPath) 207 : ('/' + servletPath + '/' + pathInfo) 208 ); 209 } 210 if (servletPath == null) 211 return ( 212 pathInfo == null 213 ? ('/' + contextRoot) 214 : ('/' + contextRoot + '/' + pathInfo) 215 ); 216 return ( 217 pathInfo == null 218 ? ('/' + contextRoot + '/' + servletPath) 219 : ('/' + contextRoot + '/' + servletPath + '/' + pathInfo) 220 ); 221 // @formatter:on 222 } 223 224 private String findAContextRoot() { 225 // @formatter:off 226 if (authority == null) 227 return rContextRoot.get(); 228 return ( 229 contextRoot == null 230 ? authority 231 : (authority + '/' + contextRoot) 232 ); 233 // @formatter:on 234 } 235 236 private String findAServletPath() { 237 // @formatter:off 238 if (authority == null) 239 return rResource.get(); 240 if (contextRoot == null) 241 return ( 242 servletPath == null 243 ? authority 244 : authority + '/' + servletPath 245 ); 246 return ( 247 servletPath == null 248 ? (authority + '/' + contextRoot) 249 : (authority + '/' + contextRoot + '/' + servletPath) 250 ); 251 // @formatter:on 252 } 253 254 private String findAPathInfo() { 255 // @formatter:off 256 if (authority == null) 257 return rPath.get(); 258 if (contextRoot == null) { 259 if (servletPath == null) 260 return ( 261 pathInfo == null 262 ? authority : (authority + '/' + pathInfo) 263 ); 264 return ( 265 pathInfo == null 266 ? (authority + '/' + servletPath) 267 : (authority + '/' + servletPath + '/' + pathInfo) 268 ); 269 } 270 if (servletPath == null) 271 return ( 272 pathInfo == null 273 ? authority + '/' + contextRoot 274 : (authority + '/' + contextRoot + '/' + pathInfo) 275 ); 276 return ( 277 pathInfo == null 278 ? (authority + '/' + contextRoot + '/' + servletPath) 279 : (authority + '/' + contextRoot + '/' + servletPath + '/' + pathInfo) 280 ); 281 // @formatter:on 282 } 283 284 /** 285 * Returns the absolute URI of just the authority portion of this URI context. 286 * 287 * <p> 288 * Example: <js>"http://hostname:port"</js> 289 * 290 * <p> 291 * If the authority is null/empty, returns <js>"/"</js>. 292 * 293 * @return 294 * The absolute URI of just the authority portion of this URI context. 295 * Never <jk>null</jk>. 296 */ 297 public String getAbsoluteAuthority() { return authority == null ? "/" : authority; } 298 299 /** 300 * Returns the absolute URI of the context-root portion of this URI context. 301 * 302 * <p> 303 * Example: <js>"http://hostname:port/context-root"</js> 304 * 305 * @return 306 * The absolute URI of the context-root portion of this URI context. 307 * Never <jk>null</jk>. 308 */ 309 public String getAbsoluteContextRoot() { 310 return aContextRoot.get(); 311 } 312 313 /** 314 * Returns the absolute URI of the path portion of this URI context. 315 * 316 * <p> 317 * Example: <js>"http://hostname:port/context-root/servlet-path/path-info"</js> 318 * 319 * @return 320 * The absolute URI of the path portion of this URI context. 321 * Never <jk>null</jk>. 322 */ 323 public String getAbsolutePathInfo() { 324 return aPathInfo.get(); 325 } 326 327 /** 328 * Returns the parent of the URL returned by {@link #getAbsolutePathInfo()}. 329 * 330 * @return The parent of the URL returned by {@link #getAbsolutePathInfo()}. 331 */ 332 public String getAbsolutePathInfoParent() { return getParent(getAbsolutePathInfo()); } 333 334 /** 335 * Returns the absolute URI of the resource portion of this URI context. 336 * 337 * <p> 338 * Example: <js>"http://hostname:port/context-root/servlet-path"</js> 339 * 340 * @return 341 * The absolute URI of the resource portion of this URI context. 342 * Never <jk>null</jk>. 343 */ 344 public String getAbsoluteServletPath() { 345 return aServletPath.get(); 346 } 347 348 /** 349 * Returns the parent of the URL returned by {@link #getAbsoluteServletPath()}. 350 * 351 * @return The parent of the URL returned by {@link #getAbsoluteServletPath()}. 352 */ 353 public String getAbsoluteServletPathParent() { return getParent(getAbsoluteServletPath()); } 354 355 /** 356 * Returns the root-relative URI of the context portion of this URI context. 357 * 358 * <p> 359 * Example: <js>"/context-root"</js> 360 * 361 * @return 362 * The root-relative URI of the context portion of this URI context. 363 * Never <jk>null</jk>. 364 */ 365 public String getRootRelativeContextRoot() { 366 return rContextRoot.get(); 367 } 368 369 /** 370 * Returns the root-relative URI of the path portion of this URI context. 371 * 372 * <p> 373 * Example: <js>"/context-root/servlet-path/path-info"</js> 374 * 375 * @return 376 * The root-relative URI of the path portion of this URI context. 377 * Never <jk>null</jk>. 378 */ 379 public String getRootRelativePathInfo() { 380 return rPath.get(); 381 } 382 383 /** 384 * Returns the parent of the URL returned by {@link #getRootRelativePathInfo()}. 385 * 386 * @return The parent of the URL returned by {@link #getRootRelativePathInfo()}. 387 */ 388 public String getRootRelativePathInfoParent() { return getParent(getRootRelativePathInfo()); } 389 390 /** 391 * Returns the root-relative URI of the resource portion of this URI context. 392 * 393 * <p> 394 * Example: <js>"/context-root/servlet-path"</js> 395 * 396 * @return 397 * The root-relative URI of the resource portion of this URI context. 398 * Never <jk>null</jk>. 399 */ 400 public String getRootRelativeServletPath() { 401 return rResource.get(); 402 } 403 404 /** 405 * Returns the parent of the URL returned by {@link #getRootRelativeServletPath()}. 406 * 407 * @return The parent of the URL returned by {@link #getRootRelativeServletPath()}. 408 */ 409 public String getRootRelativeServletPathParent() { return getParent(getRootRelativeServletPath()); } 410 411 protected FluentMap<String,Object> properties() { 412 // @formatter:off 413 return filteredBeanPropertyMap() 414 .a("aContextRoot", aContextRoot.get()) 415 .a("aPathInfo", aPathInfo.get()) 416 .a("aServletPath", aServletPath.get()) 417 .a("authority", authority) 418 .a("contextRoot", contextRoot) 419 .a("parentPath", parentPath) 420 .a("pathInfo", pathInfo) 421 .a("rContextRoot", rContextRoot.get()) 422 .a("rResource", rResource.get()) 423 .a("servletPath", servletPath) 424 .a("rPath", rPath.get()); 425 // @formatter:on 426 } 427 428 @Override /* Overridden from Object */ 429 public String toString() { 430 return r(properties()); 431 } 432}