blob: 3a5a4c2d7da1f5750e43e0d3278844fc6001846e [file] [log] [blame]
// ***************************************************************************************************************************
// * 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 = stringify(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 <c>relativeTo</c> and <c>uri</c> 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 = stringify(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;
}
}