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