This is an automated email from the ASF dual-hosted git repository. ghenzler pushed a commit to branch feature/SLING-9745-ResourceUri-Api in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-api.git
commit 298ed530c6bbd6bcc828c1c9c80790d9919c7f43 Author: georg.henzler <[email protected]> AuthorDate: Fri Sep 18 10:41:15 2020 +0200 SLING-9745 Introduce ResourceUri (immutable) and ResourceUriBuilder General purpose class to represent a ResourceUri (like e.g. a link in an html <a> tag) --- pom.xml | 1 - .../apache/sling/api/resource/uri/ResourceUri.java | 181 +++++ .../sling/api/resource/uri/ResourceUriBuilder.java | 837 +++++++++++++++++++++ .../sling/api/resource/uri/package-info.java | 23 + .../api/resource/uri/ResourceUriBuilderTest.java | 157 ++++ .../ResourceUriBuilderWithAdjustMethodTest.java | 244 ++++++ .../api/resource/uri/ResourceUriRebaseTest.java | 231 ++++++ .../sling/api/resource/uri/ResourceUriTest.java | 442 +++++++++++ ...UriToSlingRequestPathInfoCompatibilityTest.java | 272 +++++++ 9 files changed, 2387 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8e02cb7..38e6e92 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,6 @@ <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.2</version> - <scope>test</scope> </dependency> <dependency> <groupId>org.osgi</groupId> diff --git a/src/main/java/org/apache/sling/api/resource/uri/ResourceUri.java b/src/main/java/org/apache/sling/api/resource/uri/ResourceUri.java new file mode 100644 index 0000000..1250215 --- /dev/null +++ b/src/main/java/org/apache/sling/api/resource/uri/ResourceUri.java @@ -0,0 +1,181 @@ +/* + * 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.sling.api.resource.uri; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +import java.net.URI; +import java.util.Map; +import java.util.function.Consumer; + +import org.apache.sling.api.request.RequestPathInfo; +import org.apache.sling.api.resource.Resource; + +/** + * Represents an immutable URI that points to a resource or alternatively, can contain opaque URIs like {@code mailto:} or + * {@code javascript:}. Use {@link ResourceUri#adjust(Consumer)} or {@link ResourceUriBuilder} to create new or modified instances. + */ +public interface ResourceUri extends RequestPathInfo { + + /** + * @return returns the URI. + */ + public URI toUri(); + + /** + * @return returns the URI as String. + */ + public String toString(); + + /** + * @return returns the scheme of the ResourceUri or null if not set + */ + public String getScheme(); + + /** + * @return returns the user info of the ResourceUri or null if not set + */ + public String getUserInfo(); + + /** + * @return returns the host of the ResourceUri or null if not set + */ + public String getHost(); + + /** + * @return returns the port of the ResourceUri or null if not set + */ + public int getPort(); + + /** + * @return returns the resource path or null if the URI does not contain a path. + */ + @Override + public String getResourcePath(); + + /** + * @return returns the selector string or null if the URI does not contain selector(s) + */ + @Override + public String getSelectorString(); + + /** + * @return returns the selector array (empty if the URI does not contain selector(s)) + */ + @Override + public String[] getSelectors(); + + /** + * @return returns the extension or null if the URI does not contain an extension + */ + @Override + public String getExtension(); + + /** + * @return returns the path parameters or an empty Map if the URI does not contain any + */ + public Map<String, String> getPathParameters(); + + /** + * @return returns the suffix or null if the URI does not contain a suffix + */ + @Override + public String getSuffix(); + + /** + * @return returns the joint path of resource path, selectors, extension and suffix or null if resource path is not set + */ + public String getPath(); + + /** + * @return returns the query part of the uri + */ + public String getQuery(); + + /** + * @return returns the url fragment of the uri + */ + public String getFragment(); + + /** + * @return scheme specific part of the URI + */ + public String getSchemeSpecificPart(); + + /** + * @return returns the corresponding suffix resource + */ + @Override + public Resource getSuffixResource(); + + /** + * @return returns true if the uri is either a relative or absolute path (this is the case if scheme and host is empty and the URI path + * is set) + */ + default boolean isPath() { + return isBlank(getScheme()) + && isBlank(getHost()) + && isNotBlank(getResourcePath()); + } + + /** + * @return true if the uri is a absolute path starting with a slash ('/'). This is the default case for all links to pages and assets in + * AEM. + */ + default boolean isAbsolutePath() { + return isPath() && getResourcePath().startsWith(ResourceUriBuilder.CHAR_SLASH); + } + + /** + * @return true if uri is relative (not an URL and not starting with '/') + */ + default boolean isRelativePath() { + return isPath() && !getResourcePath().startsWith(ResourceUriBuilder.CHAR_SLASH); + } + + /** + * @return true if the uri is an absolute URI containing a scheme. + */ + default boolean isFullUri() { + return isNotBlank(getScheme()) + && isNotBlank(getHost()); + } + + /** + * @return true if the uri is an opaque URI like e.g. mailto: + */ + default boolean isOpaque() { + return isNotBlank(getScheme()) + && isNotBlank(getSchemeSpecificPart()); + } + + /** + * Shortcut to adjust resource URIs, e.g. {@code resourceUri = resourceUri.adjust(b -> b.setExtension("html")); }. + * + * @param builderConsumer + * @return the adjusted ResourceUri (new instance) + */ + default ResourceUri adjust(Consumer<ResourceUriBuilder> builderConsumer) { + ResourceUriBuilder builder = ResourceUriBuilder.createFrom(this); + builderConsumer.accept(builder); + return builder.build(); + } + +} diff --git a/src/main/java/org/apache/sling/api/resource/uri/ResourceUriBuilder.java b/src/main/java/org/apache/sling/api/resource/uri/ResourceUriBuilder.java new file mode 100644 index 0000000..09e5d93 --- /dev/null +++ b/src/main/java/org/apache/sling/api/resource/uri/ResourceUriBuilder.java @@ -0,0 +1,837 @@ +/* + * 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.sling.api.resource.uri; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.request.RequestPathInfo; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Builder for ResourceUris. + */ +public class ResourceUriBuilder { + + private static final String HTTPS_SCHEME = "https"; + private static final int HTTPS_DEFAULT_PORT = 443; + private static final String HTTP_SCHEME = "http"; + private static final int HTTP_DEFAULT_PORT = 80; + + static final String CHAR_HASH = "#"; + static final String CHAR_QM = "?"; + static final String CHAR_DOT = "."; + static final String CHAR_SLASH = "/"; + static final String CHAR_AT = "@"; + static final String SELECTOR_DOT_REGEX = "\\.(?!\\.?/)"; // (?!\\.?/) to avoid matching ./ and ../ + static final String CHAR_COLON = ":"; + static final String CHAR_SEMICOLON = ";"; + static final String CHAR_EQUALS = "="; + static final String CHAR_SINGLEQUOTE = "'"; + static final String PATH_PARAMETERS_REGEX = ";([a-zA-z0-9]+)=(?:\\'([^']*)\\'|([^/]+))"; + + public static ResourceUriBuilder create() { + return new ResourceUriBuilder(); + } + + /** + * Creates a builder from another ResourceUri. + * + * @param resourceUri + * @return a ResourceUriBuilder + */ + public static ResourceUriBuilder createFrom(ResourceUri resourceUri) { + return create() + .setScheme(resourceUri.getScheme()) + .setUserInfo(resourceUri.getUserInfo()) + .setHost(resourceUri.getHost()) + .setPort(resourceUri.getPort()) + .setResourcePath(resourceUri.getResourcePath()) + .setPathParameters(resourceUri.getPathParameters()) + .setSelectors(resourceUri.getSelectors()) + .setExtension(resourceUri.getExtension()) + .setSuffix(resourceUri.getSuffix()) + .setQuery(resourceUri.getQuery()) + .setFragment(resourceUri.getFragment()) + .setSchemeSpecificPart(resourceUri.getSchemeSpecificPart()) + .setResourceResolver(resourceUri instanceof ImmutableResourceUri + ? ((ImmutableResourceUri) resourceUri).getBuilder().resourceResolver + : null); + } + + /** + * Creates a builder from a Resource (only taking the resource path into account). + * + * @param resource + * @return a ResourceUriBuilder + */ + public static ResourceUriBuilder createFrom(Resource resource) { + return create() + .setResourcePath(resource.getPath()) + .setResourceResolver(resource.getResourceResolver()); + } + + /** + * Creates a builder from a RequestPathInfo instance . + * + * @param requestPathInfo + * @return a ResourceUriBuilder + */ + public static ResourceUriBuilder createFrom(RequestPathInfo requestPathInfo) { + Resource suffixResource = requestPathInfo.getSuffixResource(); + return create() + .setResourceResolver(suffixResource != null ? suffixResource.getResourceResolver() : null) + .setResourcePath(requestPathInfo.getResourcePath()) + .setSelectors(requestPathInfo.getSelectors()) + .setExtension(requestPathInfo.getExtension()) + .setSuffix(requestPathInfo.getSuffix()); + } + + /** + * Creates a builder from a request. + * + * @param request + * @return a ResourceUriBuilder + */ + public static ResourceUriBuilder createFrom(SlingHttpServletRequest request) { + return createFrom(request.getRequestPathInfo()) + .setResourceResolver(request.getResourceResolver()) + .setScheme(request.getScheme()) + .setHost(request.getServerName()) + .setPort(request.getServerPort()) + .setQuery(request.getQueryString()); + } + + /** + * Creates a builder from an arbitrary URI. + * + * @param uri + * the uri to transform to a ResourceUri + * @param resourceResolver + * a resource resolver is needed to decide up to what part the path is the resource path (that decision is only possible by + * checking against the underlying repository). If null is passed in, the shortest viable resource path is used. + * @return a ResourceUriBuilder + */ + public static ResourceUriBuilder createFrom(@NotNull URI uri, @Nullable ResourceResolver resourceResolver) { + String path = uri.getPath(); + boolean pathExists = !StringUtils.isBlank(path); + boolean schemeSpecificRelevant = !pathExists && uri.getQuery() == null; + return create() + .setResourceResolver(resourceResolver) + .setScheme(uri.getScheme()) + .setUserInfo(uri.getUserInfo()) + .setHost(uri.getHost()) + .setPort(uri.getPort()) + .setPath(pathExists ? path : null) + .setQuery(uri.getQuery()) + .setFragment(uri.getFragment()) + .setSchemeSpecificPart(schemeSpecificRelevant ? uri.getSchemeSpecificPart() : null); + } + + /** + * Creates a builder from an arbitrary URI string. + * + * @param uriStr + * to uri string to parse + * @param resourceResolver + * a resource resolver is needed to decide up to what part the path is the resource path (that decision is only possible by + * checking against the underlying repository). If null is passed in, the shortest viable resource path is used. + * @return a ResourceUriBuilder + */ + public static ResourceUriBuilder parse(@NotNull String uriStr, @Nullable ResourceResolver resourceResolver) { + URI uri; + try { + uri = new URI(uriStr); + return createFrom(uri, resourceResolver); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid URI " + uriStr + ": " + e.getMessage(), e); + } + } + + private String scheme = null; + + private String userInfo = null; + private String host = null; + private int port = -1; + + private String resourcePath = null; + private final List<String> selectors = new LinkedList<>(); + private String extension = null; + private final Map<String, String> pathParameters = new LinkedHashMap<>(); + private String suffix = null; + private String schemeSpecificPart = null; + private String query = null; + private String fragment = null; + + // only needed for getSuffixResource() from interface RequestPathInfo + private ResourceResolver resourceResolver = null; + + private ResourceUriBuilder() { + } + + /** @param userInfo + * @return the builder for method chaining */ + public ResourceUriBuilder setUserInfo(String userInfo) { + if (schemeSpecificPart != null) { + return this; + } + this.userInfo = userInfo; + return this; + } + + /** @param host + * @return the builder for method chaining */ + public ResourceUriBuilder setHost(String host) { + if (schemeSpecificPart != null) { + return this; + } + this.host = host; + return this; + } + + /** @param port + * @return the builder for method chaining */ + public ResourceUriBuilder setPort(int port) { + if (schemeSpecificPart != null) { + return this; + } + this.port = port; + return this; + } + + /** @param path + * @return the builder for method chaining */ + public ResourceUriBuilder setPath(String path) { + if (schemeSpecificPart != null) { + return this; + } + + // adds path parameters to this.pathParameters and returns path without those + path = extractPathParameters(path); + + // split in resource path, selectors, extension and suffix + Matcher dotMatcher; + if (path != null && path.startsWith(ResourceUriBuilder.CHAR_SLASH) && resourceResolver != null) { + setResourcePath(path); + rebaseResourcePath(); + } else if (path != null && (dotMatcher = Pattern.compile(SELECTOR_DOT_REGEX).matcher(path)).find()) { + int firstDotPosition = dotMatcher.start(); + setPathWithDefinedResourcePosition(path, firstDotPosition); + } else { + setResourcePath(path); + } + + return this; + } + + /** + * Will rebase the uri based on the underlying resource structure. A resource resolver is necessary for this operation, hence + * setResourceResolver() needs to be called before balanceResourcePath() or a create method that implicitly sets this has to be used. + * + * @return the builder for method chaining + * @throws IllegalStateException + * if no resource resolver is available + */ + public ResourceUriBuilder rebaseResourcePath() { + if (schemeSpecificPart != null || resourcePath == null) { + return this; + } + if (resourceResolver == null) { + throw new IllegalStateException("setResourceResolver() needs to be called before balanceResourcePath()"); + } + + String path = assemblePath(false); + ResourcePathIterator it = new ResourcePathIterator(path); + String availableResourcePath = null; + while (it.hasNext()) { + availableResourcePath = it.next(); + if (resourceResolver.getResource(availableResourcePath) != null) { + break; + } + } + + selectors.clear(); + extension = null; + suffix = null; + if (availableResourcePath.length() == path.length()) { + resourcePath = availableResourcePath; + } else { + setPathWithDefinedResourcePosition(path, availableResourcePath.length()); + } + return this; + } + + /** + * @param resourcePath + * @return the builder for method chaining + */ + public ResourceUriBuilder setResourcePath(String resourcePath) { + if (schemeSpecificPart != null) { + return this; + } + this.resourcePath = resourcePath; + return this; + } + + /** + * @param selectors + * @return the builder for method chaining + */ + public ResourceUriBuilder setSelectors(String[] selectors) { + if (schemeSpecificPart != null || resourcePath == null) { + return this; + } + this.selectors.clear(); + Arrays.stream(selectors).forEach(this.selectors::add); + return this; + } + + /** + * @param selector + * @return the builder for method chaining + */ + public ResourceUriBuilder addSelector(String selector) { + if (schemeSpecificPart != null || resourcePath == null) { + return this; + } + this.selectors.add(selector); + return this; + } + + /** + * @param extension + * @return the builder for method chaining + */ + public ResourceUriBuilder setExtension(String extension) { + if (schemeSpecificPart != null || resourcePath == null) { + return this; + } + this.extension = extension; + return this; + } + + /** + * @return returns the path parameters + */ + public ResourceUriBuilder setPathParameter(String key, String value) { + if (schemeSpecificPart != null || resourcePath == null) { + return this; + } + this.pathParameters.put(key, value); + return this; + } + + public ResourceUriBuilder setPathParameters(Map<String, String> pathParameters) { + this.pathParameters.clear(); + this.pathParameters.putAll(pathParameters); + return this; + } + + /** + * @param suffix + * @return the builder for method chaining + */ + public ResourceUriBuilder setSuffix(String suffix) { + if (schemeSpecificPart != null || resourcePath == null) { + return this; + } + if (suffix != null && !StringUtils.startsWith(suffix, "/")) { + throw new IllegalArgumentException("Suffix needs to start with slash"); + } + this.suffix = suffix; + return this; + } + + /** @param query + * @return the builder for method chaining */ + public ResourceUriBuilder setQuery(String query) { + if (schemeSpecificPart != null) { + return this; + } + this.query = query; + return this; + } + + /** @param urlFragment + * @return the builder for method chaining */ + public ResourceUriBuilder setFragment(String urlFragment) { + if (schemeSpecificPart != null) { + return this; + } + this.fragment = urlFragment; + return this; + } + + /** @param scheme + * @return the builder for method chaining */ + public ResourceUriBuilder setScheme(String scheme) { + this.scheme = scheme; + return this; + } + + /** @param schemeSpecificPart + * @return the builder for method chaining */ + public ResourceUriBuilder setSchemeSpecificPart(String schemeSpecificPart) { + if (schemeSpecificPart != null && schemeSpecificPart.isEmpty()) { + return this; + } + this.schemeSpecificPart = schemeSpecificPart; + return this; + } + + /** Will remove scheme and authority (that is user info, host and port). + * + * @return the builder for method chaining */ + public ResourceUriBuilder removeSchemeAndAuthority() { + setScheme(null); + setUserInfo(null); + setHost(null); + setPort(-1); + return this; + } + + /** Will take over scheme and authority (user info, host and port) from provided resourceUri. + * + * @param resourceUri + * @return the builder for method chaining */ + public ResourceUriBuilder useSchemeAndAuthority(ResourceUri resourceUri) { + setScheme(resourceUri.getScheme()); + setUserInfo(resourceUri.getUserInfo()); + setHost(resourceUri.getHost()); + setPort(resourceUri.getPort()); + return this; + } + + /** + * Sets the resource resolver (required for {@link RequestPathInfo#getSuffixResource()}). + * + * @param resourceResolver + * the resource resolver + * @return the builder for method chaining + */ + public ResourceUriBuilder setResourceResolver(ResourceResolver resourceResolver) { + this.resourceResolver = resourceResolver; + return this; + } + + /** Will take over scheme and authority (user info, host and port) from provided uri. + * + * @param uri + * @return the builder for method chaining */ + public ResourceUriBuilder useSchemeAndAuthority(URI uri) { + useSchemeAndAuthority(createFrom(uri, resourceResolver).build()); + return this; + } + + /** Builds the immutable ResourceUri from this builder. + * + * @return the builder for method chaining */ + public ResourceUri build() { + return new ImmutableResourceUri(); + } + + private String toStringInternal() { + StringBuilder requestUri = new StringBuilder(); + + if (StringUtils.isNotBlank(scheme)) { + requestUri.append(scheme + CHAR_COLON); + } + if (isNotBlank(scheme) && isNotBlank(host)) { + requestUri.append(CHAR_SLASH + CHAR_SLASH); + if (StringUtils.isNotBlank(userInfo)) { + requestUri.append(userInfo + CHAR_AT); + } + requestUri.append(host); + if (port > 0 + && !(scheme.equals(HTTP_SCHEME) && port == HTTP_DEFAULT_PORT) + && !(scheme.equals(HTTPS_SCHEME) && port == HTTPS_DEFAULT_PORT)) { + requestUri.append(CHAR_COLON + port); + } + } + if (resourcePath != null) { + requestUri.append(assemblePath(true)); + } + if (schemeSpecificPart != null) { + requestUri.append(schemeSpecificPart); + } + if (query != null) { + requestUri.append(CHAR_QM + query); + } + if (fragment != null) { + requestUri.append(CHAR_HASH + fragment); + } + return requestUri.toString(); + } + + /** @return string representation of builder */ + public String toString() { + return toStringInternal(); + } + + private void setPathWithDefinedResourcePosition(String path, int firstDotPositionAfterResourcePath) { + setResourcePath(path.substring(0, firstDotPositionAfterResourcePath)); + int firstSlashAfterFirstDotPosition = path.indexOf(CHAR_SLASH, firstDotPositionAfterResourcePath); + String pathWithoutSuffix = firstSlashAfterFirstDotPosition > -1 + ? path.substring(firstDotPositionAfterResourcePath + 1, firstSlashAfterFirstDotPosition) + : path.substring(firstDotPositionAfterResourcePath + 1); + String[] pathBits = pathWithoutSuffix.split(SELECTOR_DOT_REGEX); + if (pathBits.length > 1) { + setSelectors(Arrays.copyOfRange(pathBits, 0, pathBits.length - 1)); + } + setExtension(pathBits.length > 0 && pathBits[pathBits.length - 1].length() > 0 ? pathBits[pathBits.length - 1] : null); + setSuffix(firstSlashAfterFirstDotPosition > -1 ? path.substring(firstSlashAfterFirstDotPosition) : null); + } + + private String extractPathParameters(String path) { + Map<String, String> currentPathParameters = null; + if (path != null) { + Pattern pathParameterRegex = Pattern.compile(PATH_PARAMETERS_REGEX); + + StringBuffer resultString = null; + Matcher regexMatcher = pathParameterRegex.matcher(path); + while (regexMatcher.find()) { + if (resultString == null) { + resultString = new StringBuffer(); + } + if (currentPathParameters == null) { + currentPathParameters = new LinkedHashMap<>(); + } + regexMatcher.appendReplacement(resultString, ""); + String key = regexMatcher.group(1); + String value = StringUtils.defaultIfEmpty(regexMatcher.group(2), regexMatcher.group(3)); + currentPathParameters.put(key, value); + } + if (resultString != null) { + regexMatcher.appendTail(resultString); + path = resultString.toString(); + pathParameters.putAll(currentPathParameters); + } + } + return path; + } + + private String assemblePath(boolean includePathParamters) { + if (resourcePath == null) { + return null; + } + + StringBuilder pathBuilder = new StringBuilder(); + pathBuilder.append(resourcePath); + if (includePathParamters && !pathParameters.isEmpty()) { + for (Map.Entry<String, String> pathParameter : pathParameters.entrySet()) { + pathBuilder.append(CHAR_SEMICOLON + pathParameter.getKey() + CHAR_EQUALS + + CHAR_SINGLEQUOTE + pathParameter.getValue() + CHAR_SINGLEQUOTE); + } + } + + boolean dotAdded = false; + if (!selectors.isEmpty()) { + pathBuilder.append(CHAR_DOT + String.join(CHAR_DOT, selectors)); + dotAdded = true; + } + if (!StringUtils.isBlank(extension)) { + pathBuilder.append(CHAR_DOT + extension); + dotAdded = true; + } + + if (!StringUtils.isBlank(suffix)) { + if (!dotAdded) { + pathBuilder.append(CHAR_DOT); + } + pathBuilder.append(suffix); + } + return pathBuilder.toString(); + } + + + // read-only view on the builder data (to avoid another copy of the data into a new object) + private class ImmutableResourceUri implements ResourceUri { + + @Override + public String getResourcePath() { + return resourcePath; + } + + // returns null in line with + // https://sling.apache.org/apidocs/sling11/org/apache/sling/api/request/RequestPathInfo.html#getSelectorString-- + @Override + public String getSelectorString() { + return !selectors.isEmpty() ? String.join(CHAR_DOT, selectors) : null; + } + + @Override + public String[] getSelectors() { + return selectors.toArray(new String[selectors.size()]); + } + + @Override + public String getExtension() { + return extension; + } + + @Override + public Map<String, String> getPathParameters() { + return Collections.unmodifiableMap(pathParameters); + } + + @Override + public String getSuffix() { + return suffix; + } + + @Override + public String getPath() { + return assemblePath(true); + } + + @Override + public String getSchemeSpecificPart() { + return schemeSpecificPart; + } + + @Override + public String getQuery() { + return query; + } + + @Override + public String getFragment() { + return fragment; + } + + @Override + public String getScheme() { + return scheme; + } + + @Override + public String getHost() { + return host; + } + + @Override + public int getPort() { + return port; + } + + @Override + public Resource getSuffixResource() { + if (StringUtils.isNotBlank(suffix) && resourceResolver != null) { + return resourceResolver.resolve(suffix); + } else { + return null; + } + } + + @Override + public String getUserInfo() { + return userInfo; + } + + @Override + public String toString() { + return toStringInternal(); + } + + @Override + public URI toUri() { + String uriString = toString(); + try { + return new URI(uriString); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid Sling URI: " + uriString, e); + } + } + + private ResourceUriBuilder getBuilder() { + return ResourceUriBuilder.this; + } + + // generated hashCode() and equals() + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((extension == null) ? 0 : extension.hashCode()); + result = prime * result + ((fragment == null) ? 0 : fragment.hashCode()); + result = prime * result + ((host == null) ? 0 : host.hashCode()); + result = prime * result + ((pathParameters == null) ? 0 : pathParameters.hashCode()); + result = prime * result + port; + result = prime * result + ((query == null) ? 0 : query.hashCode()); + result = prime * result + ((resourcePath == null) ? 0 : resourcePath.hashCode()); + result = prime * result + ((scheme == null) ? 0 : scheme.hashCode()); + result = prime * result + ((schemeSpecificPart == null) ? 0 : schemeSpecificPart.hashCode()); + result = prime * result + ((selectors == null) ? 0 : selectors.hashCode()); + result = prime * result + ((suffix == null) ? 0 : suffix.hashCode()); + result = prime * result + ((userInfo == null) ? 0 : userInfo.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ImmutableResourceUri other = (ImmutableResourceUri) obj; + if (extension == null) { + if (other.getBuilder().extension != null) + return false; + } else if (!extension.equals(other.getBuilder().extension)) + return false; + if (fragment == null) { + if (other.getBuilder().fragment != null) + return false; + } else if (!fragment.equals(other.getBuilder().fragment)) + return false; + if (host == null) { + if (other.getBuilder().host != null) + return false; + } else if (!host.equals(other.getBuilder().host)) + return false; + if (pathParameters == null) { + if (other.getBuilder().pathParameters != null) + return false; + } else if (!pathParameters.equals(other.getBuilder().pathParameters)) + return false; + if (port != other.getBuilder().port) + return false; + if (query == null) { + if (other.getBuilder().query != null) + return false; + } else if (!query.equals(other.getBuilder().query)) + return false; + if (resourcePath == null) { + if (other.getBuilder().resourcePath != null) + return false; + } else if (!resourcePath.equals(other.getBuilder().resourcePath)) + return false; + if (scheme == null) { + if (other.getBuilder().scheme != null) + return false; + } else if (!scheme.equals(other.getBuilder().scheme)) + return false; + if (schemeSpecificPart == null) { + if (other.getBuilder().schemeSpecificPart != null) + return false; + } else if (!schemeSpecificPart.equals(other.getBuilder().schemeSpecificPart)) + return false; + if (selectors == null) { + if (other.getBuilder().selectors != null) + return false; + } else if (!selectors.equals(other.getBuilder().selectors)) + return false; + if (suffix == null) { + if (other.getBuilder().suffix != null) + return false; + } else if (!suffix.equals(other.getBuilder().suffix)) + return false; + if (userInfo == null) { + if (other.getBuilder().userInfo != null) + return false; + } else if (!userInfo.equals(other.getBuilder().userInfo)) + return false; + return true; + } + + } + + /** Iterate over a path by creating shorter segments of that path using "." as a separator. + * <p> + * For example, if path = /some/path.a4.html/xyz.ext the sequence is: + * <ol> + * <li>/some/path.a4.html/xyz.ext</li> + * <li>/some/path.a4.html/xyz</li> + * <li>/some/path.a4</li> + * <li>/some/path</li> + * </ol> + * <p> + * The root path (/) is never returned. */ + private class ResourcePathIterator implements Iterator<String> { + + // the next path to return, null if nothing more to return + private String nextPath; + + /** Creates a new instance iterating over the given path + * + * @param path The path to iterate over. If this is empty or <code>null</code> this iterator will not return anything. */ + private ResourcePathIterator(String path) { + if (path == null || path.length() == 0) { + // null or empty path, there is nothing to return + nextPath = null; + } else { + // find last non-slash character + int i = path.length() - 1; + while (i >= 0 && path.charAt(i) == '/') { + i--; + } + if (i < 0) { + // only slashes, assume root node + nextPath = "/"; + } else if (i < path.length() - 1) { + // cut off slash + nextPath = path.substring(0, i + 1); + } else { + // no trailing slash + nextPath = path; + } + } + } + + public boolean hasNext() { + return nextPath != null; + } + + public String next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + final String result = nextPath; + // find next path + int lastDot = nextPath.lastIndexOf('.'); + nextPath = (lastDot > 0) ? nextPath.substring(0, lastDot) : null; + + return result; + } + + public void remove() { + throw new UnsupportedOperationException("remove"); + } + } + +} diff --git a/src/main/java/org/apache/sling/api/resource/uri/package-info.java b/src/main/java/org/apache/sling/api/resource/uri/package-info.java new file mode 100644 index 0000000..dd21e49 --- /dev/null +++ b/src/main/java/org/apache/sling/api/resource/uri/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ +@Version("1.0.0") +package org.apache.sling.api.resource.uri; + +import org.osgi.annotation.versioning.Version; + diff --git a/src/test/java/org/apache/sling/api/resource/uri/ResourceUriBuilderTest.java b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriBuilderTest.java new file mode 100644 index 0000000..523b594 --- /dev/null +++ b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriBuilderTest.java @@ -0,0 +1,157 @@ +/* + * 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.sling.api.resource.uri; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.request.RequestPathInfo; +import org.apache.sling.api.resource.Resource; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ResourceUriBuilderTest { + + @Mock + SlingHttpServletRequest request; + + @Mock + RequestPathInfo requestPathInfo; + + @Mock + Resource resource; + + @Before + public void before() { + when(request.getRequestPathInfo()).thenReturn(requestPathInfo); + } + + @Test + public void testBasicUsage() { + + ResourceUri testUri = ResourceUriBuilder.create() + .setResourcePath("/test/to/path") + .setSelectors(new String[] { "sel1", "sel2" }) + .setExtension("html") + .setSuffix("/suffix/path") + .setQuery("par1=val1&par2=val2") + .build(); + + assertEquals("/test/to/path.sel1.sel2.html/suffix/path?par1=val1&par2=val2", testUri.toString()); + } + + // the tests in ResourceUriTest extensively test the builder's parse method by using it for constructing + // all types of ResourceUris + @Test + public void testParse() { + + String testUriStr = "https://example.com/test/to/path.sel1.sel2.html"; + ResourceUri testUri = ResourceUriBuilder.parse(testUriStr, null).build(); + assertEquals(testUriStr, testUri.toString()); + } + + @Test(expected = IllegalArgumentException.class) + public void testParseInvalidUri() { + ResourceUriBuilder.parse(":foo", null); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetInvalidSuffix() { + ResourceUriBuilder.parse("/test/to/path.sel1.html", null).setSuffix("suffixWithoutSlash"); + } + + @Test + public void testCreateFromRequest() { + + when(request.getScheme()).thenReturn("https"); + when(request.getServerName()).thenReturn("example.com"); + when(request.getServerPort()).thenReturn(443); + when(request.getQueryString()).thenReturn("par1=val1"); + when(requestPathInfo.getResourcePath()).thenReturn("/test/to/path"); + when(requestPathInfo.getSelectors()).thenReturn(new String[] { "sel1", "sel2" }); + when(requestPathInfo.getExtension()).thenReturn("html"); + when(requestPathInfo.getSuffix()).thenReturn("/suffix/path"); + + ResourceUri testUri = ResourceUriBuilder.createFrom(request).build(); + + assertEquals("https://example.com/test/to/path.sel1.sel2.html/suffix/path?par1=val1", testUri.toString()); + } + + @Test + public void testCreateFromResource() { + + when(resource.getPath()).thenReturn("/test/to/path"); + ResourceUri testUri = ResourceUriBuilder.createFrom(resource).build(); + + assertEquals("/test/to/path", testUri.getResourcePath()); + assertNull(testUri.getSelectorString()); + assertNull(testUri.getExtension()); + assertNull(testUri.getSuffix()); + } + + @Test + public void testCreateFromResourceWithDotInPath() { + + when(resource.getPath()).thenReturn("/test/to/image.jpg"); + ResourceUri testUri = ResourceUriBuilder.createFrom(resource).build(); + + assertEquals("/test/to/image.jpg", testUri.getResourcePath()); + assertNull(testUri.getSelectorString()); + assertNull(testUri.getExtension()); + assertNull(testUri.getSuffix()); + } + + @Test + public void testCreateFromPath() { + + when(request.getScheme()).thenReturn("https"); + when(request.getServerName()).thenReturn("example.com"); + when(request.getServerPort()).thenReturn(443); + when(request.getQueryString()).thenReturn("par1=val1"); + when(requestPathInfo.getResourcePath()).thenReturn("/test/to/path"); + when(requestPathInfo.getSelectors()).thenReturn(new String[] { "sel1", "sel2" }); + when(requestPathInfo.getExtension()).thenReturn("html"); + when(requestPathInfo.getSuffix()).thenReturn("/suffix/path"); + + ResourceUri testUri = ResourceUriBuilder.createFrom(request).build(); + + assertEquals("https://example.com/test/to/path.sel1.sel2.html/suffix/path?par1=val1", testUri.toString()); + } + + @Test + public void testUseSchemeAndAuthority() throws URISyntaxException { + + URI testUriToUseSchemeAndAuthorityFrom = new URI("https://example.com:8080/test/to/path.sel1.sel2.html"); + String testPath = "/path/to/page.html"; + ResourceUri testUri = ResourceUriBuilder.parse(testPath, null) + .useSchemeAndAuthority(testUriToUseSchemeAndAuthorityFrom) + .build(); + assertEquals("https://example.com:8080/path/to/page.html", testUri.toString()); + } + +} diff --git a/src/test/java/org/apache/sling/api/resource/uri/ResourceUriBuilderWithAdjustMethodTest.java b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriBuilderWithAdjustMethodTest.java new file mode 100644 index 0000000..87c233d --- /dev/null +++ b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriBuilderWithAdjustMethodTest.java @@ -0,0 +1,244 @@ +/* + * 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.sling.api.resource.uri; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.function.Consumer; + +import org.apache.sling.api.resource.ResourceResolver; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ResourceUriBuilderWithAdjustMethodTest { + + @Test + public void testAdjustAddSelectorFullUrl() { + + testAdjustUri( + "http://host.com/test/to/path.html", + resourceUriBuilder -> { + resourceUriBuilder.addSelector("test"); + }, + "http://host.com/test/to/path.test.html", + resourceUri -> { + assertEquals("test", resourceUri.getSelectorString()); + }); + } + + @Test + public void testAdjustAddSelectorAndSuffixPath() { + + testAdjustUri( + "/test/to/path.html", + resourceUriBuilder -> { + resourceUriBuilder.addSelector("test"); + resourceUriBuilder.setSuffix("/suffix/path/to/file"); + }, + "/test/to/path.test.html/suffix/path/to/file", + resourceUri -> { + assertArrayEquals(new String[] { "test" }, resourceUri.getSelectors()); + assertEquals("/suffix/path/to/file", resourceUri.getSuffix()); + }); + } + + @Test + public void testExtendSimplePathToFullUrl() { + + testAdjustUri( + "/test/to/path.html", + resourceUriBuilder -> { + resourceUriBuilder.setScheme("https"); + resourceUriBuilder.setHost("example.com"); + resourceUriBuilder.setSuffix("/suffix/path/to/file"); + }, + "https://example.com/test/to/path.html/suffix/path/to/file", + resourceUri -> { + assertEquals("https", resourceUri.getScheme()); + assertEquals("example.com", resourceUri.getHost()); + assertEquals("/suffix/path/to/file", resourceUri.getSuffix()); + }); + } + + @Test + public void testSetSelectorAndSuffixToRelativeUrl() { + + testAdjustUri( + "../to/path.html", + resourceUriBuilder -> { + resourceUriBuilder.addSelector("sel1"); + resourceUriBuilder.setSuffix("/suffix/path/to/file"); + }, + "../to/path.sel1.html/suffix/path/to/file", + resourceUri -> { + assertEquals("../to/path", resourceUri.getResourcePath()); + assertEquals("sel1", resourceUri.getSelectorString()); + assertEquals("/suffix/path/to/file", resourceUri.getSuffix()); + }); + } + + @Test + public void testFullUrltoSimplePath() { + + testAdjustUri( + "https://user:[email protected]/test/to/path.html/suffix/path/to/file", + resourceUriBuilder -> { + resourceUriBuilder.removeSchemeAndAuthority(); + }, + "/test/to/path.html/suffix/path/to/file", + resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + }); + } + + @Test + public void testAdjustPathInOpaqueUriWithoutEffect() { + + testAdjustUri( + "mailto:[email protected]", + resourceUriBuilder -> { + resourceUriBuilder.setUserInfo("user:pw"); + resourceUriBuilder.setHost("example.com"); + resourceUriBuilder.setPort(500); + resourceUriBuilder.setPath("/path/to/resource"); + resourceUriBuilder.setResourcePath("/path/to/resource"); + resourceUriBuilder.addSelector("test"); + resourceUriBuilder.setExtension("html"); + resourceUriBuilder.setSuffix("/suffix"); + }, + "mailto:[email protected]", + resourceUri -> { + assertNull(resourceUri.getHost()); + assertNull(resourceUri.getResourcePath()); + assertNull(resourceUri.getSelectorString()); + assertNull(resourceUri.getExtension()); + assertNull(resourceUri.getSuffix()); + }); + } + + @Test + public void testAdjustOpaqueToNormalUrl() { + + testAdjustUri( + "mailto:[email protected]", + resourceUriBuilder -> { + resourceUriBuilder.setSchemeSpecificPart(null); + resourceUriBuilder.setScheme("https"); + resourceUriBuilder.setHost("example.com"); + resourceUriBuilder.setPath("/path/to/resource.html"); + }, + "https://example.com/path/to/resource.html", + resourceUri -> { + assertNull(resourceUri.getSchemeSpecificPart()); + }); + } + + @Test + public void testAdjustOpaqueUri() { + + testAdjustUri( + "mailto:[email protected]", + resourceUriBuilder -> { + resourceUriBuilder.setSchemeSpecificPart("[email protected]"); + }, + "mailto:[email protected]", + resourceUri -> { + assertEquals("[email protected]", resourceUri.getSchemeSpecificPart()); + assertNull(resourceUri.getResourcePath()); + }); + } + + @Test + public void testAdjustSelectorsInFragmentOnlyUrlWithoutEffect() { + + testAdjustUri( + "#fragment", + resourceUriBuilder -> { + resourceUriBuilder.addSelector("test"); + resourceUriBuilder.setSuffix("/suffix"); + }, + "#fragment", + resourceUri -> { + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getSuffix()); + }); + } + + @Test + public void testAjustFtpUrl() { + + testAdjustUri( + "sftp://user:[email protected]:9090/some/path", + resourceUriBuilder -> { + resourceUriBuilder.setPath("/some/other/path"); + resourceUriBuilder.setPort(9091); + }, + "sftp://user:[email protected]:9091/some/other/path", + resourceUri -> { + assertEquals("/some/other/path", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(9091, resourceUri.getPort()); + }); + } + + @Test + public void testAdjustPathParameter() { + + testAdjustUri( + "/test/to/path.sel1.html/suffix/path/to/file", + resourceUriBuilder -> { + resourceUriBuilder.setPathParameter("v", "2.0"); + }, + "/test/to/path;v='2.0'.sel1.html/suffix/path/to/file", + resourceUri -> { + assertEquals("/test/to/path", resourceUri.getResourcePath()); + assertEquals("sel1", resourceUri.getSelectorString()); + assertEquals("html", resourceUri.getExtension()); + assertEquals("/suffix/path/to/file", resourceUri.getSuffix()); + assertEquals(1, resourceUri.getPathParameters().size()); + assertEquals("2.0", resourceUri.getPathParameters().get("v")); + }); + } + + // -- helper methods + + public static void testAdjustUri(String testUri, Consumer<ResourceUriBuilder> adjuster, String testUriAfterEdit, + Consumer<ResourceUri> additionalAssertions) { + testAdjustUri(testUri, adjuster, testUriAfterEdit, additionalAssertions, null); + } + + public static void testAdjustUri(String testUri, Consumer<ResourceUriBuilder> adjuster, String testUriAfterEdit, + Consumer<ResourceUri> additionalAssertions, ResourceResolver resourceResolver) { + ResourceUri resourceUri = ResourceUriBuilder.parse(testUri, resourceResolver).build(); + + ResourceUri adjustedResourceUri = resourceUri.adjust(adjuster); + + assertEquals(testUriAfterEdit, adjustedResourceUri.toString()); + assertEquals(testUriAfterEdit, adjustedResourceUri.toUri().toString()); + + additionalAssertions.accept(adjustedResourceUri); + } + +} diff --git a/src/test/java/org/apache/sling/api/resource/uri/ResourceUriRebaseTest.java b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriRebaseTest.java new file mode 100644 index 0000000..ea2e986 --- /dev/null +++ b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriRebaseTest.java @@ -0,0 +1,231 @@ +/* + * 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.sling.api.resource.uri; + +import static org.apache.sling.api.resource.uri.ResourceUriTest.testUri; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import java.net.URISyntaxException; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class ResourceUriRebaseTest { + + private static final String FULL_URI = "/test/to/file.ext.sel1.json/suffix/path.js"; + + @Mock + ResourceResolver resolver; + + @Mock + Resource resource; + + @Test + public void testRebaseResourcePathSimplePath() { + String testUriStrSimple = "/test/to/file"; + when(resolver.getResource("/test/to/file")).thenReturn(resource); + testUri(testUriStrSimple, true, true, false, false, false, resourceUri -> { + assertEquals("/test/to/file", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + }, resolver); + } + + @Test + public void testRebaseResourcePathSimpleResourcePathIncludesExtension() { + + String testUriStrSimpleFile = "/test/to/file.css"; + when(resolver.getResource("/test/to/file.css")).thenReturn(resource); + when(resolver.getResource("/test/to/file")).thenReturn(null); + testUri(testUriStrSimpleFile, true, true, false, false, false, resourceUri -> { + assertEquals("/test/to/file.css", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + }, resolver); + } + + @Test + public void testRebaseResourcePathSimpleResourcePathExcludesExtension() { + String testUriStrSimplePage = "/path/to/page.html"; + when(resolver.getResource("/path/to/page.html")).thenReturn(null); + when(resolver.getResource("/path/to/page")).thenReturn(resource); + testUri(testUriStrSimplePage, true, true, false, false, false, resourceUri -> { + assertEquals("/path/to/page", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals("html", resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + }, resolver); + } + + @Test + public void testRebaseResourcePathFullPathIsResource() { + + // pull path with suffix is resource path + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(resource); + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext")).thenReturn(null); + when(resolver.getResource("/test/to/file")).thenReturn(null); + testUri(FULL_URI, true, true, false, false, false, resourceUri -> { + assertEquals("/test/to/file.ext.sel1.json/suffix/path.js", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + }, resolver); + } + + @Test + public void testRebaseResourcePathPartOfSuffixIsResource() { + // + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path")).thenReturn(resource); + when(resolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext")).thenReturn(null); + when(resolver.getResource("/test/to/file")).thenReturn(null); + testUri(FULL_URI, true, true, false, false, false, resourceUri -> { + assertEquals("/test/to/file.ext.sel1.json/suffix/path", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals("js", resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + }, resolver); + } + + @Test + public void testRebaseResourcePathWithSelectorsAndExtension() { + // if the resource for before the suffix part exits, it is ignored + // compare org.apache.sling.resourceresolver.impl.helper.ResourcePathIteratorTest.testMixed() + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(resource); + when(resolver.getResource("/test/to/file.ext.sel1")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext")).thenReturn(null); + when(resolver.getResource("/test/to/file")).thenReturn(null); + testUri(FULL_URI, true, true, false, false, false, resourceUri -> { + assertEquals("/test/to/file", resourceUri.getResourcePath()); + assertEquals("ext.sel1", resourceUri.getSelectorString()); + assertEquals("json", resourceUri.getExtension()); + assertEquals("/suffix/path.js", resourceUri.getSuffix()); + }, resolver); + + } + + @Test + public void testRebaseResourcePathContainsTwoDots() { + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1")).thenReturn(resource); + when(resolver.getResource("/test/to/file.ext")).thenReturn(null); + when(resolver.getResource("/test/to/file")).thenReturn(null); + testUri(FULL_URI, true, true, false, false, false, resourceUri -> { + assertEquals("/test/to/file.ext.sel1", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals("json", resourceUri.getExtension()); + assertEquals("/suffix/path.js", resourceUri.getSuffix()); + }, resolver); + + } + + @Test + public void testRebaseResourcePathContainsExtension() { + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext")).thenReturn(resource); + when(resolver.getResource("/test/to/file")).thenReturn(null); + testUri(FULL_URI, true, true, false, false, false, resourceUri -> { + assertEquals("/test/to/file.ext", resourceUri.getResourcePath()); + assertEquals("sel1", resourceUri.getSelectorString()); + assertEquals("json", resourceUri.getExtension()); + assertEquals("/suffix/path.js", resourceUri.getSuffix()); + }, resolver); + + } + + @Test + public void testRebaseResourcePathWithoutDot() { + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext")).thenReturn(null); + when(resolver.getResource("/test/to/file")).thenReturn(resource); + testUri(FULL_URI, true, true, false, false, false, resourceUri -> { + assertEquals("/test/to/file", resourceUri.getResourcePath()); + assertEquals("ext.sel1", resourceUri.getSelectorString()); + assertEquals("json", resourceUri.getExtension()); + assertEquals("/suffix/path.js", resourceUri.getSuffix()); + }, resolver); + } + + @Test + public void testRebaseResourcePathLongestMatchingPathWins() { + // side by side resources in same folder: the longest path wins + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1")).thenReturn(resource); + when(resolver.getResource("/test/to/file.ext")).thenReturn(resource); + when(resolver.getResource("/test/to/file")).thenReturn(resource); + testUri(FULL_URI, true, true, false, false, false, resourceUri -> { + assertEquals("/test/to/file.ext.sel1", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals("json", resourceUri.getExtension()); + assertEquals("/suffix/path.js", resourceUri.getSuffix()); + }, resolver); + } + + @Test + public void testRebaseResourcePathNoResourceExistsAtAll() { + // if no resource exists at all the first dot has to be taken as split + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json/suffix/path")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext.sel1")).thenReturn(null); + when(resolver.getResource("/test/to/file.ext")).thenReturn(null); + when(resolver.getResource("/test/to/file")).thenReturn(null); + testUri(FULL_URI, true, true, false, false, false, resourceUri -> { + assertEquals("/test/to/file", resourceUri.getResourcePath()); + assertEquals("ext.sel1", resourceUri.getSelectorString()); + assertEquals("json", resourceUri.getExtension()); + assertEquals("/suffix/path.js", resourceUri.getSuffix()); + }, resolver); + } + + @Test(expected = IllegalStateException.class) + public void testRebaseNotAllowedWithoutResolver() throws URISyntaxException { + + String testPath = "/path/to/page.html"; + ResourceUriBuilder.parse(testPath, null) + .rebaseResourcePath() + .build(); + } + +} diff --git a/src/test/java/org/apache/sling/api/resource/uri/ResourceUriTest.java b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriTest.java new file mode 100644 index 0000000..18cd6e3 --- /dev/null +++ b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriTest.java @@ -0,0 +1,442 @@ +/* + * 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.sling.api.resource.uri; + +import static java.util.Arrays.asList; +import static org.junit.Assert.assertEquals; + +import java.util.List; +import java.util.function.Consumer; + +import org.apache.sling.api.resource.ResourceResolver; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ResourceUriTest { + + @Mock + ResourceResolver resolver; + + @Test + public void testFullResourceUri() { + + String testUriStr = "http://host.com/test/to/path.html"; + testUri(testUriStr, false, false, false, true, false, resourceUri -> { + assertEquals("http", resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals("host.com", resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals("/test/to/path", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals("html", resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals(null, resourceUri.getQuery()); + assertEquals(null, resourceUri.getFragment()); + }, asList(resolver, null)); + + } + + @Test + public void testFullResourceUriComplex() { + + String testUriStr = "https://test:[email protected]:888/test/to/path.sel1.json/suffix/path?p1=2&p2=3#frag3939"; + testUri(testUriStr, false, false, false, true, false, resourceUri -> { + assertEquals("https", resourceUri.getScheme()); + assertEquals("test:pw", resourceUri.getUserInfo()); + assertEquals("host.com", resourceUri.getHost()); + assertEquals(888, resourceUri.getPort()); + assertEquals("/test/to/path", resourceUri.getResourcePath()); + assertEquals("sel1", resourceUri.getSelectorString()); + assertEquals("json", resourceUri.getExtension()); + assertEquals("/suffix/path", resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals("p1=2&p2=3", resourceUri.getQuery()); + assertEquals("frag3939", resourceUri.getFragment()); + }, asList(resolver, null)); + + } + + @Test + public void testAbsolutePathResourceUri() { + String testUriStr = "/test/to/path.sel1.json/suffix/path?p1=2&p2=3#frag3939"; + + testUri(testUriStr, true, true, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals("/test/to/path", resourceUri.getResourcePath()); + assertEquals("sel1", resourceUri.getSelectorString()); + assertEquals("json", resourceUri.getExtension()); + assertEquals("/suffix/path", resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals("p1=2&p2=3", resourceUri.getQuery()); + assertEquals("frag3939", resourceUri.getFragment()); + }, asList(resolver, null)); + } + + @Test + public void testResourceUriSuffixWithDots() { + + String testUriStr = "/test/to/path.min.js/suffix/app.nodesbrowser.js"; + testUri(testUriStr, true, true, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals("/test/to/path", resourceUri.getResourcePath()); + assertEquals("min", resourceUri.getSelectorString()); + assertEquals("js", resourceUri.getExtension()); + assertEquals("/suffix/app.nodesbrowser.js", resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals(null, resourceUri.getQuery()); + assertEquals(null, resourceUri.getFragment()); + }, asList(resolver, null)); + } + + @Test + public void testResourceUriMultipleDots() { + + String testUriStr = "/test/to/path.sel1.sel2..sel4.js"; + testUri(testUriStr, true, true, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals("/test/to/path", resourceUri.getResourcePath()); + assertEquals(4, resourceUri.getSelectors().length); + assertEquals("sel1.sel2..sel4", resourceUri.getSelectorString()); + assertEquals("js", resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals(null, resourceUri.getQuery()); + assertEquals(null, resourceUri.getFragment()); + }, asList(resolver, null)); + + String testUriStr2 = "/test/to/path.sel1.sel2../sel4.js"; + testUri(testUriStr2, true, true, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals("/test/to/path", resourceUri.getResourcePath()); + assertEquals(1, resourceUri.getSelectors().length); + assertEquals("sel1", resourceUri.getSelectorString()); + assertEquals("sel2", resourceUri.getExtension()); + assertEquals("/sel4.js", resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals(null, resourceUri.getQuery()); + assertEquals(null, resourceUri.getFragment()); + }, null, true); + } + + @Test + public void testRelativePathResourceUri() { + String testUriStr = "../path.html#frag1"; + + testUri(testUriStr, true, false, true, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals("../path", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals("html", resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals(null, resourceUri.getQuery()); + assertEquals("frag1", resourceUri.getFragment()); + }, asList(resolver, null)); + } + + @Test + public void testRelativePathResourceUriComplex() { + String testUriStr = "../path/./deep/path/../path.sel1.sel2.html?test=1#frag1"; + + testUri(testUriStr, true, false, true, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals("../path/./deep/path/../path", resourceUri.getResourcePath()); + assertEquals("sel1.sel2", resourceUri.getSelectorString()); + assertEquals("html", resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals("test=1", resourceUri.getQuery()); + assertEquals("frag1", resourceUri.getFragment()); + }, asList(resolver, null)); + } + + @Test + public void testAbsolutePathWithPathParameter() { + String testUriStr = "/test/to/path;v='1.0'.sel1.html/suffix/path?p1=2&p2=3#frag3939"; + + testUri(testUriStr, true, true, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals("/test/to/path", resourceUri.getResourcePath()); + assertEquals("sel1", resourceUri.getSelectorString()); + assertEquals("html", resourceUri.getExtension()); + assertEquals(1, resourceUri.getPathParameters().size()); + assertEquals("1.0", resourceUri.getPathParameters().get("v")); + assertEquals("/suffix/path", resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals("p1=2&p2=3", resourceUri.getQuery()); + assertEquals("frag3939", resourceUri.getFragment()); + }, asList(resolver, null)); + + String testUriStr2 = "/test/to/file;foo='bar'.sel1.sel2.json/suffix/path"; + testUri(testUriStr2, true, true, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals("/test/to/file", resourceUri.getResourcePath()); + assertEquals("sel1.sel2", resourceUri.getSelectorString()); + assertEquals("json", resourceUri.getExtension()); + assertEquals(1, resourceUri.getPathParameters().size()); + assertEquals("bar", resourceUri.getPathParameters().get("foo")); + assertEquals("/suffix/path", resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals(null, resourceUri.getQuery()); + assertEquals(null, resourceUri.getFragment()); + }, asList(resolver, null)); + } + + @Test + public void testAbsolutePathWithPathParameterMultiple() { + String testUriStr = "/test/to/path;v='1.0';antotherParam='test/nested';antotherParam2='7'.sel1.html/suffix/path?p1=2&p2=3#frag3939"; + + testUri(testUriStr, true, true, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals("/test/to/path", resourceUri.getResourcePath()); + assertEquals("sel1", resourceUri.getSelectorString()); + assertEquals("html", resourceUri.getExtension()); + + assertEquals(3, resourceUri.getPathParameters().size()); + assertEquals("1.0", resourceUri.getPathParameters().get("v")); + assertEquals("test/nested", resourceUri.getPathParameters().get("antotherParam")); + assertEquals("7", resourceUri.getPathParameters().get("antotherParam2")); + + assertEquals("/suffix/path", resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals("p1=2&p2=3", resourceUri.getQuery()); + assertEquals("frag3939", resourceUri.getFragment()); + }, asList(resolver, null)); + } + + @Test + public void testAbsolutePathWithPathParameterAfterExtension() { + String testUriStr = "/test/to/path.sel1.html;v='1.0'/suffix/path?p1=2&p2=3#frag3939"; + + ResourceUri testUri = testUri(testUriStr, true, true, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals("/test/to/path", resourceUri.getResourcePath()); + assertEquals("sel1", resourceUri.getSelectorString()); + assertEquals("html", resourceUri.getExtension()); + assertEquals(1, resourceUri.getPathParameters().size()); + assertEquals("1.0", resourceUri.getPathParameters().get("v")); + assertEquals("/suffix/path", resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals("p1=2&p2=3", resourceUri.getQuery()); + assertEquals("frag3939", resourceUri.getFragment()); + }, null, true /* URL is restructured (parameter moved to end), assertion below */); + + assertEquals("/test/to/path;v='1.0'.sel1.html/suffix/path?p1=2&p2=3#frag3939", testUri.toString()); + + } + + @Test + public void testJavascriptUri() { + String testUriStr = "javascript:void(0)"; + + testUri(testUriStr, false, false, false, false, true, resourceUri -> { + assertEquals("javascript", resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + assertEquals("void(0)", resourceUri.getSchemeSpecificPart()); + assertEquals(null, resourceUri.getQuery()); + assertEquals(null, resourceUri.getFragment()); + }, asList(resolver, null)); + } + + @Test + public void testMailtotUri() { + String testUriStr = "mailto:[email protected]"; + + testUri(testUriStr, false, false, false, false, true, resourceUri -> { + assertEquals("mailto", resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + assertEquals("[email protected]", resourceUri.getSchemeSpecificPart()); + assertEquals(null, resourceUri.getQuery()); + assertEquals(null, resourceUri.getFragment()); + }, asList(resolver, null)); + } + + @Test + public void testHashOnlyUri() { + + testUri("#", false, false, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals(null, resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals(null, resourceUri.getQuery()); + assertEquals("", resourceUri.getFragment()); + }, asList(resolver, null)); + + testUri("#fragment", false, false, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals(null, resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals(null, resourceUri.getQuery()); + assertEquals("fragment", resourceUri.getFragment()); + }, asList(resolver, null)); + } + + @Test + public void testQueryOnlyUri() { + + testUri("?", false, false, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals(null, resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals("", resourceUri.getQuery()); + assertEquals(null, resourceUri.getFragment()); + }, asList(resolver, null)); + + testUri("?test=test", false, false, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getScheme()); + assertEquals(null, resourceUri.getUserInfo()); + assertEquals(null, resourceUri.getHost()); + assertEquals(-1, resourceUri.getPort()); + assertEquals(null, resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + assertEquals(null, resourceUri.getSchemeSpecificPart()); + assertEquals("test=test", resourceUri.getQuery()); + assertEquals(null, resourceUri.getFragment()); + }, asList(resolver, null)); + } + + @Test + public void testUnusualQueryFragmentCombinations() { + testUri("?#", false, false, false, false, false, resourceUri -> { + assertEquals("", resourceUri.getQuery()); + assertEquals("", resourceUri.getFragment()); + }, asList(resolver, null)); + testUri("?t=2#", false, false, false, false, false, resourceUri -> { + assertEquals("t=2", resourceUri.getQuery()); + assertEquals("", resourceUri.getFragment()); + }, asList(resolver, null)); + testUri("?#t=3", false, false, false, false, false, resourceUri -> { + assertEquals("", resourceUri.getQuery()); + assertEquals("t=3", resourceUri.getFragment()); + }, asList(resolver, null)); + testUri("", false, false, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getQuery()); + assertEquals(null, resourceUri.getFragment()); + }, asList(resolver, null)); + } + + // -- helper methods + public static void testUri(String testUri, boolean isPath, boolean isAbsolutePath, boolean isRelativePath, boolean isFullUri, + boolean isOpaque, Consumer<ResourceUri> additionalAssertions, List<ResourceResolver> resourceResolvers) { + for (ResourceResolver rr : resourceResolvers) { + testUri(testUri, isPath, isAbsolutePath, isRelativePath, isFullUri, isOpaque, additionalAssertions, rr); + } + } + + public static ResourceUri testUri(String testUri, boolean isPath, boolean isAbsolutePath, boolean isRelativePath, boolean isFullUri, + boolean isOpaque, Consumer<ResourceUri> additionalAssertions) { + return testUri(testUri, isPath, isAbsolutePath, isRelativePath, isFullUri, isOpaque, additionalAssertions, (ResourceResolver) null); + } + + public static ResourceUri testUri(String testUri, boolean isPath, boolean isAbsolutePath, boolean isRelativePath, boolean isFullUri, + boolean isOpaque, Consumer<ResourceUri> additionalAssertions, ResourceResolver resourceResolver) { + return testUri(testUri, isPath, isAbsolutePath, isRelativePath, isFullUri, isOpaque, additionalAssertions, resourceResolver, false); + } + + public static ResourceUri testUri(String testUri, boolean isPath, boolean isAbsolutePath, boolean isRelativePath, boolean isFullUri, + boolean isOpaque, Consumer<ResourceUri> additionalAssertions, ResourceResolver resourceResolver, boolean urlIsRestructured) { + ResourceUri resourceUri = ResourceUriBuilder.parse(testUri, resourceResolver).build(); + + if (!urlIsRestructured) { + assertEquals("Uri toString() same as input", testUri, resourceUri.toString()); + assertEquals("Uri toUri().toString() same as input", testUri, resourceUri.toUri().toString()); + } + + assertEquals("isPath()", isPath, resourceUri.isPath()); + assertEquals("isAbsolutePath()", isAbsolutePath, resourceUri.isAbsolutePath()); + assertEquals("isRelativePath()", isRelativePath, resourceUri.isRelativePath()); + assertEquals("isFullUri()", isFullUri, resourceUri.isFullUri()); + assertEquals("isOpaque()", isOpaque, resourceUri.isOpaque()); + assertEquals("isOpaque() matches to java URI impl", resourceUri.toUri().isOpaque(), resourceUri.isOpaque()); + + additionalAssertions.accept(resourceUri); + + ResourceUri resourceUriParsedFromSameInput = ResourceUriBuilder.parse(testUri, resourceResolver).build(); + assertEquals("uris parsed from same input are expected to be equal", resourceUriParsedFromSameInput, resourceUri); + assertEquals("uris parsed from same input are expected to have the same hash code", resourceUriParsedFromSameInput.hashCode(), + resourceUri.hashCode()); + + return resourceUri; + } + +} diff --git a/src/test/java/org/apache/sling/api/resource/uri/ResourceUriToSlingRequestPathInfoCompatibilityTest.java b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriToSlingRequestPathInfoCompatibilityTest.java new file mode 100644 index 0000000..00ba823 --- /dev/null +++ b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriToSlingRequestPathInfoCompatibilityTest.java @@ -0,0 +1,272 @@ +/* + * 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.sling.api.resource.uri; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.when; + +import org.apache.sling.api.request.RequestPathInfo; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +// Replicates the same test cases as there are in SlingRequestPathInfoTest (from sling engine) +// to ensure compatibility +@RunWith(MockitoJUnitRunner.class) +public class ResourceUriToSlingRequestPathInfoCompatibilityTest { + + @Mock + ResourceResolver resourceResolver; + + @Mock + Resource resource; + + private RequestPathInfo createResourceUri(String resolutionPath, String resolutionPathInfo) { + when(resourceResolver.getResource(resolutionPath)).thenReturn(resource); + return ResourceUriBuilder.parse(resolutionPath + (resolutionPathInfo != null ? resolutionPathInfo : ""), resourceResolver).build(); + } + + @Test + public void testTrailingDot() { + RequestPathInfo p = createResourceUri("/some/path", "."); + assertEquals("/some/path", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertNull("Extension is null", p.getExtension()); + assertNull("Suffix is null", p.getSuffix()); + } + + @Test + public void testTrailingDotWithSuffix() { + RequestPathInfo p = createResourceUri("/some/path", "./suffix"); + assertEquals("/some/path", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertNull("Extension is null", p.getExtension()); + assertEquals("/suffix", p.getSuffix()); + } + + @Test + public void testTrailingDotDot() { + RequestPathInfo p = createResourceUri("/some/path", ".."); + assertEquals("/some/path", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertNull("Extension is null", p.getExtension()); + assertNull("Suffix is null", p.getSuffix()); + } + + @Test + public void testTrailingDotDotWithSuffix() { + RequestPathInfo p = createResourceUri("/some/path", "../suffix"); + assertEquals("/some/path", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertNull("Extension is null", p.getExtension()); + assertEquals("/suffix", p.getSuffix()); + } + + @Test + public void testTrailingDotDotDot() { + RequestPathInfo p = createResourceUri("/some/path", "..."); + assertEquals("/some/path", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertNull("Extension is null", p.getExtension()); + assertNull("Suffix is null", p.getSuffix()); + } + + @Test + public void testTrailingDotDotDotWithSuffix() { + RequestPathInfo p = createResourceUri("/some/path", ".../suffix"); + assertEquals("/some/path", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertNull("Extension is null", p.getExtension()); + assertEquals("/suffix", p.getSuffix()); + // the path changes slightly, the '.' is needed to still mark the suffix as suffix + assertEquals("/some/path./suffix", p.toString()); + } + + @Test + public void testAllOptions() { + RequestPathInfo p = createResourceUri("/some/path", ".print.a4.html/some/suffix"); + assertEquals("/some/path", p.getResourcePath()); + assertEquals("print.a4", p.getSelectorString()); + assertEquals(2, p.getSelectors().length); + assertEquals("print", p.getSelectors()[0]); + assertEquals("a4", p.getSelectors()[1]); + assertEquals("html", p.getExtension()); + assertEquals("/some/suffix", p.getSuffix()); + } + + @Test + public void testAllEmpty() { + RequestPathInfo p = createResourceUri("/", null); + assertEquals("/", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertNull("Extension is null", p.getExtension()); + assertNull("Suffix is null", p.getSuffix()); + } + + @Test + public void testPathOnly() { + RequestPathInfo p = createResourceUri("/some/path/here", ""); + assertEquals("/some/path/here", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertNull("Extension is null", p.getExtension()); + assertNull("Suffix is null", p.getSuffix()); + } + + @Test + public void testPathWithExtensionOnly() { + RequestPathInfo p = createResourceUri("/some/path/here.html", ""); + assertEquals("/some/path/here.html", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertNull("Extension is null", p.getExtension()); + assertNull("Suffix is null", p.getSuffix()); + } + + @Test + public void testPathAndExtensionOnly() { + RequestPathInfo p = createResourceUri("/some/path/here", ".html"); + assertEquals("/some/path/here", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertEquals("html", p.getExtension()); + assertNull("Suffix is null", p.getSuffix()); + } + + @Test + public void testPathAndOneSelectorOnly() { + RequestPathInfo p = createResourceUri("/some/path/here", ".print.html"); + assertEquals("/some/path/here", p.getResourcePath()); + assertEquals("print", p.getSelectorString()); + assertEquals(1, p.getSelectors().length); + assertEquals("print", p.getSelectors()[0]); + assertEquals("html", p.getExtension()); + assertNull("Suffix is null", p.getSuffix()); + } + + @Test + public void testPathExtAndSuffix() { + RequestPathInfo p = createResourceUri("/some/path/here", ".html/something"); + assertEquals("/some/path/here", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertEquals("html", p.getExtension()); + assertEquals("/something", p.getSuffix()); + } + + @Test + public void testSelectorsSplit() { + RequestPathInfo p = createResourceUri("/some/path", ".print.a4.html/some/suffix"); + assertEquals("/some/path", p.getResourcePath()); + assertEquals(2, p.getSelectors().length); + assertEquals("print", p.getSelectors()[0]); + assertEquals("a4", p.getSelectors()[1]); + assertEquals("html", p.getExtension()); + assertEquals("/some/suffix", p.getSuffix()); + } + + @Test + public void testPartialResolutionB() { + RequestPathInfo p = createResourceUri("/some/path", ".print.a4.html/some/suffix"); + assertEquals("/some/path", p.getResourcePath()); + assertEquals("print.a4", p.getSelectorString()); + assertEquals(2, p.getSelectors().length); + assertEquals("print", p.getSelectors()[0]); + assertEquals("a4", p.getSelectors()[1]); + assertEquals("html", p.getExtension()); + assertEquals("/some/suffix", p.getSuffix()); + } + + @Test + public void testPartialResolutionC() { + RequestPathInfo p = createResourceUri("/some/path.print", ".a4.html/some/suffix"); + assertEquals("/some/path.print", p.getResourcePath()); + assertEquals("a4", p.getSelectorString()); + assertEquals(1, p.getSelectors().length); + assertEquals("a4", p.getSelectors()[0]); + assertEquals("html", p.getExtension()); + assertEquals("/some/suffix", p.getSuffix()); + } + + @Test + public void testPartialResolutionD() { + RequestPathInfo p = createResourceUri("/some/path.print.a4", ".html/some/suffix"); + assertEquals("/some/path.print.a4", p.getResourcePath()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals(0, p.getSelectors().length); + assertEquals("html", p.getExtension()); + assertEquals("/some/suffix", p.getSuffix()); + } + + @Test + public void testDotsAroundSuffix() { + RequestPathInfo p = createResourceUri("/libs/foo/content/something/formitems", ".json/image/vnd/xnd/knd.xml"); + assertEquals("/libs/foo/content/something/formitems", p.getResourcePath()); + assertEquals("json", p.getExtension()); + assertNull("Selectors are null", p.getSelectorString()); + assertEquals("/image/vnd/xnd/knd.xml", p.getSuffix()); + } + + @Test + public void testJIRA_250_a() { + RequestPathInfo p = createResourceUri("/bunkai", ".1.json"); + assertEquals("/bunkai", p.getResourcePath()); + assertEquals("json", p.getExtension()); + assertEquals("1", p.getSelectorString()); + } + + @Test + public void testJIRA_250_b() { + RequestPathInfo p = createResourceUri("/", ".1.json"); + assertEquals("/", p.getResourcePath()); + assertEquals("json", p.getExtension()); + assertNull("Suffix is null", p.getSuffix()); + assertEquals("Selector string must not be null", "1", + p.getSelectorString()); + } + + @Test + public void testJIRA_250_c() { + RequestPathInfo p = createResourceUri("/", ".1.json/my/suffix"); + assertEquals("/", p.getResourcePath()); + assertEquals("json", p.getExtension()); + assertEquals("/my/suffix", p.getSuffix()); + assertEquals("Selector string must not be null", "1", + p.getSelectorString()); + } + + @Test + public void testJIRA_250_d() { + RequestPathInfo p = createResourceUri("/", ".json"); + assertEquals("/", p.getResourcePath()); + assertEquals("json", p.getExtension()); + assertNull("Suffix is null", p.getSuffix()); + assertNull("Selectors are null", p.getSelectorString()); + } + +}
