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}