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