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