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.UriRelativity.*;
020import static org.apache.juneau.UriResolution.*;
021import static org.apache.juneau.common.utils.ThrowableUtils.*;
022import static org.apache.juneau.common.utils.Utils.*;
023
024import java.io.*;
025import java.net.*;
026
027import org.apache.juneau.common.utils.*;
028
029/**
030 * Class used to create absolute and root-relative URIs based on your current URI 'location' and rules about how to
031 * make such resolutions.
032 *
033 * <p>
034 * Combines a {@link UriContext} instance with rules for resolution ({@link UriResolution} and relativity
035 * ({@link UriRelativity}) to define simple {@link #resolve(Object)} and {@link #append(Appendable, Object)} methods.
036 *
037 * <p>
038 * Three special protocols are used to represent context-root-relative, servlet-relative, and request-path-relative
039 * URIs:
040 *    <js>"context:/"</js>, <js>"servlet:/"</js>, and <js>"request:/"</js>.
041 *
042 * <p>
043 * The following list shows the protocols of URLs that can be resolved with this class:
044 * <ul>
045 *    <li><js>"foo://foo"</js> - Absolute URI.
046 *    <li><js>"/foo"</js> - Root-relative URI.
047 *    <li><js>"/"</js> - Root URI.
048 *    <li><js>"context:/foo"</js> - Context-root-relative URI.
049 *    <li><js>"context:/"</js> - Context-root URI.
050 *    <li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
051 *    <li><js>"servlet:/"</js> - Servlet-path URI.
052 *    <li><js>"request:/foo"</js> - Request-path-relative URI.
053 *    <li><js>"request:/"</js> - Request-path URI.
054 *    <li><js>"foo"</js> - Path-info-relative URI.
055 *    <li><js>""</js> - Path-info URI.
056 * </ul>
057 *
058 * <h5 class='section'>See Also:</h5><ul>
059 * </ul>
060 */
061public class UriResolver {
062
063   private final UriResolution resolution;
064   private final UriRelativity relativity;
065   private final String authority, contextRoot, servletPath, pathInfo, parentPath;
066
067   /**
068    * Static creator.
069    *
070    * @param resolution Rule on how URIs should be resolved.
071    * @param relativity Rule on what relative URIs are relative to.
072    * @param uriContext Current URI context (i.e. the current URI 'location').
073    * @return A new {@link UriResolver} object.
074    */
075   public static UriResolver of(UriResolution resolution, UriRelativity relativity, UriContext uriContext) {
076      return new UriResolver(resolution, relativity, uriContext);
077   }
078
079   /**
080    * Constructor.
081    *
082    * @param resolution Rule on how URIs should be resolved.
083    * @param relativity Rule on what relative URIs are relative to.
084    * @param uriContext Current URI context (i.e. the current URI 'location').
085    */
086   public UriResolver(UriResolution resolution, UriRelativity relativity, UriContext uriContext) {
087      this.resolution = resolution;
088      this.relativity = relativity;
089      this.authority = uriContext.authority;
090      this.contextRoot = uriContext.contextRoot;
091      this.servletPath = uriContext.servletPath;
092      this.pathInfo = uriContext.pathInfo;
093      this.parentPath = uriContext.parentPath;
094   }
095
096   /**
097    * Converts the specified URI to absolute form based on values in this context.
098    *
099    * @param uri
100    *    The URI to convert to absolute form.
101    *    Can be any of the following:
102    *    <ul>
103    *       <li>{@link java.net.URI}
104    *       <li>{@link java.net.URL}
105    *       <li>{@link CharSequence}
106    *    </ul>
107    *    URI can be any of the following forms:
108    *    <ul>
109    *       <li><js>"foo://foo"</js> - Absolute URI.
110    *       <li><js>"/foo"</js> - Root-relative URI.
111    *       <li><js>"/"</js> - Root URI.
112    *       <li><js>"context:/foo"</js> - Context-root-relative URI.
113    *       <li><js>"context:/"</js> - Context-root URI.
114    *       <li><js>"servlet:/foo"</js> - Servlet-path-relative URI.
115    *       <li><js>"servlet:/"</js> - Servlet-path URI.
116    *       <li><js>"request:/foo"</js> - Request-path-relative URI.
117    *       <li><js>"request:/"</js> - Request-path URI.
118    *       <li><js>"foo"</js> - Path-info-relative URI.
119    *       <li><js>""</js> - Path-info URI.
120    *    </ul>
121    * @return The converted URI.
122    */
123   public String resolve(Object uri) {
124      return resolve(uri, resolution);
125   }
126
127   private String resolve(Object uri, UriResolution res) {
128      String s = s(uri);
129      if (StringUtils.isAbsoluteUri(s))
130         return hasDotSegments(s) && res != NONE ? normalize(s) : s;
131      if (res == ROOT_RELATIVE && StringUtils.startsWith(s, '/'))
132         return hasDotSegments(s) ? normalize(s) : s;
133      if (res == NONE && ! isSpecialUri(s))
134         return s;
135      return append(new StringBuilder(), s).toString();
136   }
137
138   /**
139    * Relativizes a URI.
140    *
141    * <p>
142    * Similar to {@link URI#relativize(URI)}, except supports special protocols (e.g. <js>"servlet:/"</js>) for both
143    * the <c>relativeTo</c> and <c>uri</c> parameters.
144    *
145    * <p>
146    * For example, to relativize a URI to its servlet-relative form:
147    * <p class='bjava'>
148    *    <jc>// relativeUri == "path/foo"</jc>
149    *    String <jv>relativeUri</jv> = <jv>resolver</jv>.relativize(<js>"servlet:/"</js>, <js>"/context/servlet/path/foo"</js>);
150    * </p>
151    *
152    * @param relativeTo The URI to relativize against.
153    * @param uri The URI to relativize.
154    * @return The relativized URI.
155    */
156   public String relativize(Object relativeTo, Object uri) {
157      String r = resolve(relativeTo, ABSOLUTE);
158      String s = resolve(uri, ABSOLUTE);
159      return URI.create(r).relativize(URI.create(s)).toString();
160   }
161
162   /**
163    * Same as {@link #resolve(Object)} except appends result to the specified appendable.
164    *
165    * @param a The appendable to append the URL to.
166    * @param o The URI to convert to absolute form.
167    * @return The same appendable passed in.
168    */
169   public Appendable append(Appendable a, Object o) {
170
171      try {
172         String uri = s(o);
173         uri = nullIfEmpty(uri);
174         boolean needsNormalize = hasDotSegments(uri) && resolution != null;
175
176         // Absolute paths are not changed.
177         if (StringUtils.isAbsoluteUri(uri))
178            return a.append(needsNormalize ? normalize(uri) : uri);
179         if (resolution == NONE && ! isSpecialUri(uri))
180            return a.append(emptyIfNull(uri));
181         if (resolution == ROOT_RELATIVE && StringUtils.startsWith(uri, '/'))
182            return a.append(needsNormalize ? normalize(uri) : uri);
183
184         Appendable a2 = needsNormalize ? new StringBuilder() : a;
185
186         // Root-relative path
187         if (StringUtils.startsWith(uri, '/')) {
188            if (authority != null)
189               a2.append(authority);
190            if (uri.length() != 1)
191               a2.append(uri);
192            else if (authority == null)
193               a2.append('/');
194         }
195
196         // Context-relative path
197         else if (uri != null && uri.startsWith("context:/")) {
198            if (resolution == ABSOLUTE && authority != null)
199               a2.append(authority);
200            if (contextRoot != null)
201               a2.append('/').append(contextRoot);
202            if (uri.length() > 9)
203               a2.append('/').append(uri.substring(9));
204            else if (contextRoot == null && (authority == null || resolution != ABSOLUTE))
205               a2.append('/');
206         }
207
208         // Resource-relative path
209         else if (uri != null && uri.startsWith("servlet:/")) {
210            if (resolution == ABSOLUTE && authority != null)
211               a2.append(authority);
212            if (contextRoot != null)
213               a2.append('/').append(contextRoot);
214            if (servletPath != null)
215               a2.append('/').append(servletPath);
216            if (uri.length() > 9)
217               a2.append('/').append(uri.substring(9));
218            else if (servletPath == null && contextRoot == null && (authority == null || resolution != ABSOLUTE))
219               a2.append('/');
220         }
221
222         // Request-relative path
223         else if (uri != null && uri.startsWith("request:/")) {
224            if (resolution == ABSOLUTE && authority != null)
225               a2.append(authority);
226            if (contextRoot != null)
227               a2.append('/').append(contextRoot);
228            if (servletPath != null)
229               a2.append('/').append(servletPath);
230            if (pathInfo != null)
231               a2.append('/').append(pathInfo);
232            if (uri.length() > 9)
233               a2.append('/').append(uri.substring(9));
234            else if (servletPath == null && contextRoot == null && pathInfo == null && (authority == null || resolution != ABSOLUTE))
235               a2.append('/');
236         }
237
238         // Relative path
239         else {
240            if (resolution == ABSOLUTE && authority != null)
241               a2.append(authority);
242            if (contextRoot != null)
243               a2.append('/').append(contextRoot);
244            if (servletPath != null)
245               a2.append('/').append(servletPath);
246            if (relativity == RESOURCE && uri != null)
247               a2.append('/').append(uri);
248            else if (relativity == PATH_INFO) {
249               if (uri == null) {
250                  if (pathInfo != null)
251                     a2.append('/').append(pathInfo);
252               } else {
253                  if (parentPath != null)
254                     a2.append('/').append(parentPath);
255                  a2.append('/').append(uri);
256               }
257            }
258            else if (uri == null && contextRoot == null && servletPath == null && (authority == null || resolution != ABSOLUTE))
259               a2.append('/');
260         }
261
262         if (needsNormalize)
263            a.append(normalize(a2.toString()));
264
265         return a;
266      } catch (IOException e) {
267         throw asRuntimeException(e);
268      }
269   }
270
271   private static boolean isSpecialUri(String s) {
272      if (s == null || s.isEmpty())
273         return false;
274      char c = s.charAt(0);
275      if (c != 's' && c != 'c' && c != 'r')
276         return false;
277      return s.startsWith("servlet:/") || s.startsWith("context:/") || s.startsWith("request:/");
278   }
279
280   private static String normalize(String s) {
281      s = URI.create(s).normalize().toString();
282      if (s.length() > 1 && s.charAt(s.length()-1) == '/')
283         s = s.substring(0, s.length()-1);
284      return s;
285   }
286
287   private static boolean hasDotSegments(String s) {
288      if (s == null)
289         return false;
290      for (int i = 0; i < s.length()-1; i++) {
291         char c = s.charAt(i);
292         if ((i == 0 && c == '/') || (c == '/' && s.charAt(i+1) == '.'))
293            return true;
294      }
295      return false;
296   }
297}