| // *************************************************************************************************************************** |
| // * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * |
| // * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * |
| // * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * |
| // * with the License. You may obtain a copy of the License at * |
| // * * |
| // * http://www.apache.org/licenses/LICENSE-2.0 * |
| // * * |
| // * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * |
| // * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * |
| // * specific language governing permissions and limitations under the License. * |
| // *************************************************************************************************************************** |
| package org.apache.juneau; |
| |
| import static org.apache.juneau.UriRelativity.*; |
| import static org.apache.juneau.UriResolution.*; |
| import static org.apache.juneau.internal.StringUtils.*; |
| |
| import java.io.*; |
| import java.net.*; |
| |
| /** |
| * Class used to create absolute and root-relative URIs based on your current URI 'location' and rules about how to |
| * make such resolutions. |
| * |
| * <p> |
| * Combines a {@link UriContext} instance with rules for resolution ({@link UriResolution} and relativity |
| * ({@link UriRelativity}) to define simple {@link #resolve(Object)} and {@link #append(Appendable, Object)} methods. |
| * |
| * <p> |
| * Three special protocols are used to represent context-root-relative, servlet-relative, and request-path-relative |
| * URIs: |
| * <js>"context:/"</js>, <js>"servlet:/"</js>, and <js>"request:/"</js>. |
| * |
| * <p> |
| * The following list shows the protocols of URLs that can be resolved with this class: |
| * <ul> |
| * <li><js>"foo://foo"</js> - Absolute URI. |
| * <li><js>"/foo"</js> - Root-relative URI. |
| * <li><js>"/"</js> - Root URI. |
| * <li><js>"context:/foo"</js> - Context-root-relative URI. |
| * <li><js>"context:/"</js> - Context-root URI. |
| * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. |
| * <li><js>"servlet:/"</js> - Servlet-path URI. |
| * <li><js>"request:/foo"</js> - Request-path-relative URI. |
| * <li><js>"request:/"</js> - Request-path URI. |
| * <li><js>"foo"</js> - Path-info-relative URI. |
| * <li><js>""</js> - Path-info URI. |
| * </ul> |
| */ |
| public class UriResolver { |
| |
| private final UriResolution resolution; |
| private final UriRelativity relativity; |
| private final String authority, contextRoot, servletPath, pathInfo, parentPath; |
| |
| /** |
| * Constructor. |
| * |
| * @param resolution Rule on how URIs should be resolved. |
| * @param relativity Rule on what relative URIs are relative to. |
| * @param uriContext Current URI context (i.e. the current URI 'location'). |
| */ |
| public UriResolver(UriResolution resolution, UriRelativity relativity, UriContext uriContext) { |
| this.resolution = resolution; |
| this.relativity = relativity; |
| this.authority = uriContext.authority; |
| this.contextRoot = uriContext.contextRoot; |
| this.servletPath = uriContext.servletPath; |
| this.pathInfo = uriContext.pathInfo; |
| this.parentPath = uriContext.parentPath; |
| } |
| |
| /** |
| * Converts the specified URI to absolute form based on values in this context. |
| * |
| * @param uri |
| * The URI to convert to absolute form. |
| * Can be any of the following: |
| * <ul> |
| * <li>{@link java.net.URI} |
| * <li>{@link java.net.URL} |
| * <li>{@link CharSequence} |
| * </ul> |
| * URI can be any of the following forms: |
| * <ul> |
| * <li><js>"foo://foo"</js> - Absolute URI. |
| * <li><js>"/foo"</js> - Root-relative URI. |
| * <li><js>"/"</js> - Root URI. |
| * <li><js>"context:/foo"</js> - Context-root-relative URI. |
| * <li><js>"context:/"</js> - Context-root URI. |
| * <li><js>"servlet:/foo"</js> - Servlet-path-relative URI. |
| * <li><js>"servlet:/"</js> - Servlet-path URI. |
| * <li><js>"request:/foo"</js> - Request-path-relative URI. |
| * <li><js>"request:/"</js> - Request-path URI. |
| * <li><js>"foo"</js> - Path-info-relative URI. |
| * <li><js>""</js> - Path-info URI. |
| * </ul> |
| * @return The converted URI. |
| */ |
| public String resolve(Object uri) { |
| return resolve(uri, resolution); |
| } |
| |
| private String resolve(Object uri, UriResolution res) { |
| String s = asString(uri); |
| if (isAbsoluteUri(s)) |
| return hasDotSegments(s) && res != NONE ? normalize(s) : s; |
| if (res == ROOT_RELATIVE && startsWith(s, '/')) |
| return hasDotSegments(s) ? normalize(s) : s; |
| if (res == NONE && ! isSpecialUri(s)) |
| return s; |
| return append(new StringBuilder(), s).toString(); |
| } |
| |
| /** |
| * Relativizes a URI. |
| * |
| * <p> |
| * Similar to {@link URI#relativize(URI)}, except supports special protocols (e.g. <js>"servlet:/"</js>) for both |
| * the <code>relativeTo</code> and <code>uri</code> parameters. |
| * |
| * <p> |
| * For example, to relativize a URI to its servlet-relative form: |
| * <p class='bcode w800'> |
| * <jc>// relativeUri == "path/foo"</jc> |
| * String relativeUri = resolver.relativize(<js>"servlet:/"</js>, <js>"/context/servlet/path/foo"</js>); |
| * </p> |
| * |
| * @param relativeTo The URI to relativize against. |
| * @param uri The URI to relativize. |
| * @return The relativized URI. |
| */ |
| public String relativize(Object relativeTo, Object uri) { |
| String r = resolve(relativeTo, ABSOLUTE); |
| String s = resolve(uri, ABSOLUTE); |
| return URI.create(r).relativize(URI.create(s)).toString(); |
| } |
| |
| /** |
| * Same as {@link #resolve(Object)} except appends result to the specified appendable. |
| * |
| * @param a The appendable to append the URL to. |
| * @param o The URI to convert to absolute form. |
| * @return The same appendable passed in. |
| */ |
| public Appendable append(Appendable a, Object o) { |
| |
| try { |
| String uri = asString(o); |
| uri = nullIfEmpty(uri); |
| boolean needsNormalize = hasDotSegments(uri) && resolution != null; |
| |
| // Absolute paths are not changed. |
| if (isAbsoluteUri(uri)) |
| return a.append(needsNormalize ? normalize(uri) : uri); |
| if (resolution == NONE && ! isSpecialUri(uri)) |
| return a.append(emptyIfNull(uri)); |
| if (resolution == ROOT_RELATIVE && startsWith(uri, '/')) |
| return a.append(needsNormalize ? normalize(uri) : uri); |
| |
| Appendable a2 = needsNormalize ? new StringBuilder() : a; |
| |
| // Root-relative path |
| if (startsWith(uri, '/')) { |
| if (authority != null) |
| a2.append(authority); |
| if (uri.length() != 1) |
| a2.append(uri); |
| else if (authority == null) |
| a2.append('/'); |
| } |
| |
| // Context-relative path |
| else if (uri != null && uri.startsWith("context:/")) { |
| if (resolution == ABSOLUTE && authority != null) |
| a2.append(authority); |
| if (contextRoot != null) |
| a2.append('/').append(contextRoot); |
| if (uri.length() > 9) |
| a2.append('/').append(uri.substring(9)); |
| else if (contextRoot == null && (authority == null || resolution != ABSOLUTE)) |
| a2.append('/'); |
| } |
| |
| // Resource-relative path |
| else if (uri != null && uri.startsWith("servlet:/")) { |
| if (resolution == ABSOLUTE && authority != null) |
| a2.append(authority); |
| if (contextRoot != null) |
| a2.append('/').append(contextRoot); |
| if (servletPath != null) |
| a2.append('/').append(servletPath); |
| if (uri.length() > 9) |
| a2.append('/').append(uri.substring(9)); |
| else if (servletPath == null && contextRoot == null && (authority == null || resolution != ABSOLUTE)) |
| a2.append('/'); |
| } |
| |
| // Request-relative path |
| else if (uri != null && uri.startsWith("request:/")) { |
| if (resolution == ABSOLUTE && authority != null) |
| a2.append(authority); |
| if (contextRoot != null) |
| a2.append('/').append(contextRoot); |
| if (servletPath != null) |
| a2.append('/').append(servletPath); |
| if (pathInfo != null) |
| a2.append('/').append(pathInfo); |
| if (uri.length() > 9) |
| a2.append('/').append(uri.substring(9)); |
| else if (servletPath == null && contextRoot == null && pathInfo == null && (authority == null || resolution != ABSOLUTE)) |
| a2.append('/'); |
| } |
| |
| // Relative path |
| else { |
| if (resolution == ABSOLUTE && authority != null) |
| a2.append(authority); |
| if (contextRoot != null) |
| a2.append('/').append(contextRoot); |
| if (servletPath != null) |
| a2.append('/').append(servletPath); |
| if (relativity == RESOURCE && uri != null) |
| a2.append('/').append(uri); |
| else if (relativity == PATH_INFO) { |
| if (uri == null) { |
| if (pathInfo != null) |
| a2.append('/').append(pathInfo); |
| } else { |
| if (parentPath != null) |
| a2.append('/').append(parentPath); |
| a2.append('/').append(uri); |
| } |
| } |
| else if (uri == null && contextRoot == null && servletPath == null && (authority == null || resolution != ABSOLUTE)) |
| a2.append('/'); |
| } |
| |
| if (needsNormalize) |
| a.append(normalize(a2.toString())); |
| |
| return a; |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| private static boolean isSpecialUri(String s) { |
| if (s == null || s.length() == 0) |
| return false; |
| char c = s.charAt(0); |
| if (c != 's' && c != 'c' && c != 'r') |
| return false; |
| return s.startsWith("servlet:/") || s.startsWith("context:/") || s.startsWith("request:/"); |
| } |
| |
| private static String normalize(String s) { |
| s = URI.create(s).normalize().toString(); |
| if (s.length() > 1 && s.charAt(s.length()-1) == '/') |
| s = s.substring(0, s.length()-1); |
| return s; |
| } |
| |
| private static boolean hasDotSegments(String s) { |
| if (s == null) |
| return false; |
| for (int i = 0; i < s.length()-1; i++) { |
| char c = s.charAt(i); |
| if (i == 0 && c == '/') |
| return true; |
| if (c == '/' && s.charAt(i+1) == '.') |
| return true; |
| } |
| return false; |
| } |
| } |