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