This is an automated email from the ASF dual-hosted git repository. ghenzler pushed a commit to branch feature/SLING-9662-Introduce-Resource-Mapping-SPI-v2 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-api.git
commit c27db47a07ce7d85896e1c761f462653a7532e79 Author: georg.henzler <[email protected]> AuthorDate: Mon Sep 7 16:01:00 2020 +0200 SLING-9662 Introduce Resource Mapping SPI incl. ResourceUri --- pom.xml | 2 +- .../sling/api/resource/ResourceResolver.java | 6 +- .../resource/mapping/PathToUriMappingService.java | 69 +++ .../sling/api/resource/mapping/package-info.java | 2 +- .../apache/sling/api/resource/package-info.java | 2 +- .../apache/sling/api/resource/uri/ResourceUri.java | 122 ++++ .../sling/api/resource/uri/ResourceUriBuilder.java | 607 ++++++++++++++++++ .../sling/api/resource/{ => uri}/package-info.java | 5 +- .../spi/resource/mapping/MappingChainContext.java | 51 ++ .../spi/resource/mapping/ResourceUriMapper.java | 56 ++ .../resource/mapping/package-info.java | 4 +- .../sling/api/resource/uri/ResourceUriTest.java | 679 +++++++++++++++++++++ ...UriToSlingRequestPathInfoCompatibilityTest.java | 271 ++++++++ 13 files changed, 1866 insertions(+), 10 deletions(-) diff --git a/pom.xml b/pom.xml index 8e02cb7..10f3109 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ <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/ResourceResolver.java b/src/main/java/org/apache/sling/api/resource/ResourceResolver.java index 8a2b27d..e92abe0 100644 --- a/src/main/java/org/apache/sling/api/resource/ResourceResolver.java +++ b/src/main/java/org/apache/sling/api/resource/ResourceResolver.java @@ -22,12 +22,14 @@ import java.io.Closeable; import java.util.Iterator; import java.util.Map; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; import javax.servlet.http.HttpServletRequest; import org.apache.sling.api.adapter.Adaptable; import org.apache.sling.api.resource.mapping.ResourceMapper; +import org.apache.sling.api.resource.uri.ResourceUri; +import org.apache.sling.api.resource.uri.ResourceUriBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.osgi.annotation.versioning.ProviderType; /** diff --git a/src/main/java/org/apache/sling/api/resource/mapping/PathToUriMappingService.java b/src/main/java/org/apache/sling/api/resource/mapping/PathToUriMappingService.java new file mode 100644 index 0000000..8134699 --- /dev/null +++ b/src/main/java/org/apache/sling/api/resource/mapping/PathToUriMappingService.java @@ -0,0 +1,69 @@ +/* + * 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.mapping; + +import java.util.Map; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.sling.api.resource.uri.ResourceUri; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; + +/** Provides a way to resolve URIs to resource paths and map resource paths to URIs. */ +@ProviderType +public interface PathToUriMappingService { + + /** A context hint for a consumer of the map/resolve result */ + @ProviderType + public interface ContextHint { + String getName(); + } + + /** The result of a map or resolve operation */ + @ProviderType + public interface Result { + /** @return the ResourceUri */ + @NotNull + ResourceUri getResourceUri(); + + /** @return context hints (e.g. 'invalid link' for map(), or 'requires authentication' for resolve()) */ + @NotNull + Set<ContextHint> getContextHints(); + + /** @return all intermediate mappings as produced by {@link org.apache.sling.spi.resource.mapping.ResourceUriMapper} services. */ + @NotNull + Map<String, ResourceUri> getIntermediateMappings(); + } + + /** Resolves a path relative to the given request. + * + * @param request + * @param path + * @return a @{link PathToUriMappingService.Result} */ + Result resolve(@Nullable HttpServletRequest request, @NotNull String path); + + /** @param request + * @param resourcePath + * @return a @{link PathToUriMappingService.Result} */ + Result map(@Nullable HttpServletRequest request, @NotNull String resourcePath); + +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/api/resource/mapping/package-info.java b/src/main/java/org/apache/sling/api/resource/mapping/package-info.java index e871225..00a1ea1 100644 --- a/src/main/java/org/apache/sling/api/resource/mapping/package-info.java +++ b/src/main/java/org/apache/sling/api/resource/mapping/package-info.java @@ -17,7 +17,7 @@ * under the License. */ -@Version("1.0.1") +@Version("1.1.0") package org.apache.sling.api.resource.mapping; import org.osgi.annotation.versioning.Version; diff --git a/src/main/java/org/apache/sling/api/resource/package-info.java b/src/main/java/org/apache/sling/api/resource/package-info.java index 7bd85e6..ac05b61 100644 --- a/src/main/java/org/apache/sling/api/resource/package-info.java +++ b/src/main/java/org/apache/sling/api/resource/package-info.java @@ -17,7 +17,7 @@ * under the License. */ -@Version("2.12.2") +@Version("2.13.0") package org.apache.sling.api.resource; import org.osgi.annotation.versioning.Version; 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..858ac31 --- /dev/null +++ b/src/main/java/org/apache/sling/api/resource/uri/ResourceUri.java @@ -0,0 +1,122 @@ +/* + * 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 special URIs like {@code mailto:} or + * {@code javascript:}. */ +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 link */ + public String getScheme(); + + /** @return returns the user info of the link */ + public String getUserInfo(); + + /** @return returns the host of the link */ + public String getHost(); + + /** @return returns the port of the link */ + public int getPort(); + + /** @return returns the resource path or null if link does not contain path. */ + @Override + public String getResourcePath(); + + /** @return returns the selector string */ + @Override + public String getSelectorString(); + + /** @return returns the selector array */ + @Override + public String[] getSelectors(); + + /** @return returns the extension of the link */ + @Override + public String getExtension(); + + /** @return returns the path parameters */ + public Map<String, String> getPathParameters(); + + /** @return returns the suffix of the link */ + @Override + public String getSuffix(); + + /** @return returns the query part of the link */ + public String getQuery(); + + /** @return returns the url fragment of the link */ + public String getFragment(); + + /** @return scheme specific part of the URL */ + public String getSchemeSpecificPart(); + + /** @return returns the corresponding */ + @Override + public Resource getSuffixResource(); + + /** @return returns true if the link 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 link 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 link is relative (not an URL and not starting with '/') */ + default boolean isRelativePath() { + return isPath() && !getResourcePath().startsWith(ResourceUriBuilder.CHAR_SLASH); + } + + /** @return true if the link is an absolute URI containing a scheme. */ + default boolean isFullUri() { + return isNotBlank(getScheme()) + && isNotBlank(getHost()); + } + + /** @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..bc9c4c6 --- /dev/null +++ b/src/main/java/org/apache/sling/api/resource/uri/ResourceUriBuilder.java @@ -0,0 +1,607 @@ +/* + * 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 java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +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; + +public class ResourceUriBuilder { + + 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 = "'"; + + 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) { + return create() + .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()); + } + + /** Creates a builder from a URI. + * + * @param uri + * @return a ResourceUriBuilder */ + public static ResourceUriBuilder createFrom(URI uri, 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 resourceUriStr + * @return a ResourceUriBuilder */ + public static ResourceUriBuilder parse(String resourceUriStr, ResourceResolver resourceResolver) { + URI uri; + try { + uri = new URI(resourceUriStr); + return createFrom(uri, resourceResolver); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid URI " + resourceUriStr + ": " + e.getMessage(), e); + } + } + + /** Creates a builder from a resource path. + * + * @param resourcePathStr + * @return a ResourceUriBuilder */ + public static ResourceUriBuilder forPath(String resourcePathStr) { + return new ResourceUriBuilder().setPath(resourcePathStr); + } + + 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; + } + + // path parameters + Map<String, String> currentPathParameters = null; + if (path != null) { + Pattern pathParameterRegex = Pattern.compile(";([a-zA-z0-9]+)=(?:\\'([^']*)\\'|([^/]+))"); + + 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); + } + } + + // regular RequestPathInfo + Matcher dotMatcher; + if (path != null && (dotMatcher = Pattern.compile(SELECTOR_DOT_REGEX).matcher(path)).find()) { + int firstDotPosition = dotMatcher.start(); + int firstSlashAfterFirstDotPosition = path.indexOf(CHAR_SLASH, firstDotPosition); + String pathWithoutSuffix = firstSlashAfterFirstDotPosition > -1 ? path.substring(0, firstSlashAfterFirstDotPosition) : path; + String[] pathBits = pathWithoutSuffix.split(SELECTOR_DOT_REGEX); + setResourcePath(pathBits[0]); + if (pathBits.length > 2) { + setSelectors(Arrays.copyOfRange(pathBits, 1, pathBits.length - 1)); + } + setExtension(pathBits.length > 1 ? pathBits[pathBits.length - 1] : null); + setSuffix(firstSlashAfterFirstDotPosition > -1 ? path.substring(firstSlashAfterFirstDotPosition) : null); + } else { + setResourcePath(path); + } + + if (resourceResolver != null) { + balanceResourcePath(); + } + + return this; + } + + public ResourceUriBuilder balanceResourcePath() { + if (schemeSpecificPart != null) { + return this; + } + if (resourceResolver == null) { + throw new IllegalStateException("setResourceResolver() needs to be called before balanceResourcePath()"); + } + List<String> potentialResourcePathBits = new ArrayList<>(); + potentialResourcePathBits.add(resourcePath); + potentialResourcePathBits.addAll(selectors); + if (extension != null) { + potentialResourcePathBits.add(extension); + } + String fullPathWithSuffix = String.join(".", potentialResourcePathBits) + suffix; + if (resourceResolver.getResource(fullPathWithSuffix) != null) { + this.resourcePath = fullPathWithSuffix; + selectors.clear(); + extension = null; + suffix = null; + } else { + for (int i = potentialResourcePathBits.size(); i > 1; i--) { + String potentialResourcePath = String.join(".", potentialResourcePathBits.subList(0, i)); + if (resourceResolver.getResource(potentialResourcePath) != null) { + this.resourcePath = potentialResourcePath; + selectors.clear(); + extension = null; + List<String> remainingList = potentialResourcePathBits.subList(i, potentialResourcePathBits.size()); + if (!remainingList.isEmpty()) { + extension = remainingList.get(remainingList.size() - 1); + selectors.addAll(remainingList.subList(0, remainingList.size() - 1)); + } + break; + } + } + } + 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; + } + + // only to support getSuffixResource() from interface RequestPathInfo + private 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(); + } + + /** @return string representation of builder */ + public String toString() { + return build().toString(); + } + + // read-only view on the builder data (to avoid another copy of the data into a new object) + private class ImmutableResourceUri implements ResourceUri { + + 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; + + @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 pathParameters; + } + + @Override + public String getSuffix() { + return suffix; + } + + @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() { + StringBuilder requestUri = new StringBuilder(); + + if (StringUtils.isNotBlank(scheme)) { + requestUri.append(scheme + CHAR_COLON); + } + if (isFullUri()) { + 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(resourcePath); + } + if (!pathParameters.isEmpty()) { + for (Map.Entry<String, String> pathParameter : pathParameters.entrySet()) { + requestUri.append(CHAR_SEMICOLON + pathParameter.getKey() + CHAR_EQUALS + + CHAR_SINGLEQUOTE + pathParameter.getValue() + CHAR_SINGLEQUOTE); + } + } + + if (!selectors.isEmpty()) { + requestUri.append(CHAR_DOT + String.join(CHAR_DOT, selectors)); + } + if (!StringUtils.isBlank(extension)) { + requestUri.append(CHAR_DOT + extension); + } + + if (!StringUtils.isBlank(suffix)) { + requestUri.append(suffix); + } + 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(); + } + + @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; + } + + } + +} diff --git a/src/main/java/org/apache/sling/api/resource/package-info.java b/src/main/java/org/apache/sling/api/resource/uri/package-info.java similarity index 93% copy from src/main/java/org/apache/sling/api/resource/package-info.java copy to src/main/java/org/apache/sling/api/resource/uri/package-info.java index 7bd85e6..dd21e49 100644 --- a/src/main/java/org/apache/sling/api/resource/package-info.java +++ b/src/main/java/org/apache/sling/api/resource/uri/package-info.java @@ -16,9 +16,8 @@ * specific language governing permissions and limitations * under the License. */ - -@Version("2.12.2") -package org.apache.sling.api.resource; +@Version("1.0.0") +package org.apache.sling.api.resource.uri; import org.osgi.annotation.versioning.Version; diff --git a/src/main/java/org/apache/sling/spi/resource/mapping/MappingChainContext.java b/src/main/java/org/apache/sling/spi/resource/mapping/MappingChainContext.java new file mode 100644 index 0000000..cf6d541 --- /dev/null +++ b/src/main/java/org/apache/sling/spi/resource/mapping/MappingChainContext.java @@ -0,0 +1,51 @@ +/* + * 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.spi.resource.mapping; + +import java.util.Map; + +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.mapping.PathToUriMappingService.ContextHint; +import org.apache.sling.api.resource.uri.ResourceUri; +import org.osgi.annotation.versioning.ProviderType; + +/** Provides ResourceToUriMapper instances with additional context. */ +@ProviderType +public interface MappingChainContext { + + /** May be called by any ResourceUriMapper in the chain to indicate that the rest of the chain should be skipped. */ + void skipRemainingChain(); + + /** Add @{link ContextHint} (e.g. 'invalid link' for map(), or 'requires authentication' for resolve()) */ + void addContextHint(ContextHint contextHint); + + /** The resource resolver that was used to call map() or resolve(). */ + ResourceResolver getResourceResolver(); + + /** Allows to share state between ResourceToUriMapper instances in the chain. + * + * @return a mutable map to share state (never null). */ + Map<String, Object> getAttributes(); + + /** Provides access to intermediate mappings. + * + * @return the resource mappings */ + Map<String, ResourceUri> getIntermediateMappings(); + +} diff --git a/src/main/java/org/apache/sling/spi/resource/mapping/ResourceUriMapper.java b/src/main/java/org/apache/sling/spi/resource/mapping/ResourceUriMapper.java new file mode 100644 index 0000000..d3fed13 --- /dev/null +++ b/src/main/java/org/apache/sling/spi/resource/mapping/ResourceUriMapper.java @@ -0,0 +1,56 @@ +/* + * 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.spi.resource.mapping; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.sling.api.resource.uri.ResourceUri; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ConsumerType; + +/** SPI interface that contributes to resource mapping and resolving of the resource resolver's map() and resolve() methods. + * + * All registered services build a conceptual chain sorted by service ranking. The resource URI is passed through the chain while any + * ResourceUriMapper chain member may or may not make adjustments to the resource URI. + * + * rr.resolve() passes through the chain starting at the ResourceUriMapper with the <strong>highest</strong> service ranking and rr.map() + * passes through the chain starting at the ResourceUriMapper with the <strong>lowest</strong> service ranking */ +@ConsumerType +public interface ResourceUriMapper { + + /** Contributes to the map process, may or may not make adjustments to the resource URI. + * + * @param resourceUri the URI to be mapped + * @param request the request to be taken as example + * @param context can be used to skip further processing of the chain or for sharing state between instances of ResourceUriMapper + * services + * @return the adjusted ResourceUri or if no adjustments are necessary, just return resourceUri as passed in by first parameter */ + ResourceUri map(@NotNull ResourceUri resourceUri, @Nullable HttpServletRequest request, @NotNull MappingChainContext context); + + /** Contributes to the resolve process, may or may not make adjustments to the resource URI + * + * @param resourceUri the URI to be resolved + * @param request the request context that may or may not influence the resolution process (request may be null) + * @param context can be used to skip further processing of the chain or for sharing state between instances of ResourceUriMapper + * services + * @return the adjusted ResourceUri or if no adjustments are necessary, just return resourceUri as passed in by first parameter */ + ResourceUri resolve(@NotNull ResourceUri resourceUri, @Nullable HttpServletRequest request, @NotNull MappingChainContext context); + +} diff --git a/src/main/java/org/apache/sling/api/resource/mapping/package-info.java b/src/main/java/org/apache/sling/spi/resource/mapping/package-info.java similarity index 92% copy from src/main/java/org/apache/sling/api/resource/mapping/package-info.java copy to src/main/java/org/apache/sling/spi/resource/mapping/package-info.java index e871225..c8c07af 100644 --- a/src/main/java/org/apache/sling/api/resource/mapping/package-info.java +++ b/src/main/java/org/apache/sling/spi/resource/mapping/package-info.java @@ -17,8 +17,8 @@ * under the License. */ -@Version("1.0.1") -package org.apache.sling.api.resource.mapping; +@Version("1.0.0") +package org.apache.sling.spi.resource.mapping; import org.osgi.annotation.versioning.Version; 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..3711631 --- /dev/null +++ b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriTest.java @@ -0,0 +1,679 @@ +/* + * 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.mockito.Mockito.when; + +import java.util.function.Consumer; + +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 ResourceUriTest { + + @Mock + ResourceResolver resourceResolver; + + @Mock + Resource resource; + + @Test + public void testFullResourceUri() { + + String testUriStr = "http://host.com/test/to/path.html"; + testUri(testUriStr, false, false, false, true, 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()); + }); + + } + + @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, 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()); + }); + + } + + @Test + public void testAbsolutePathResourceUri() { + String testUriStr = "/test/to/path.sel1.json/suffix/path?p1=2&p2=3#frag3939"; + + testUri(testUriStr, true, true, 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()); + }); + } + + @Test + public void testResourceUriSuffixWithDots() { + + String testUriStr = "/test/to/path.min.js/suffix/app.nodesbrowser.js"; + testUri(testUriStr, true, true, 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()); + }); + } + + @Test + public void testResourceUriMultipleDots() { + + String testUriStr = "/test/to/path.sel1.sel2..sel4.js"; + testUri(testUriStr, true, true, 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()); + }); + + String testUriStr2 = "/test/to/path.sel1.sel2../sel4.js"; + testUri(testUriStr2, true, true, 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()); + }, true); + } + + @Test + public void testRelativePathResourceUri() { + String testUriStr = "../path.html#frag1"; + + testUri(testUriStr, true, false, true, 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()); + }); + } + + @Test + public void testRelativePathResourceUriComplex() { + String testUriStr = "../path/./deep/path/../path.sel1.sel2.html?test=1#frag1"; + + testUri(testUriStr, true, false, true, 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()); + }); + } + + @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, 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()); + }); + + String testUriStr2 = "/test/to/file;foo='bar'.sel1.sel2.json/suffix/path"; + testUri(testUriStr2, true, true, 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()); + }); + } + + @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, 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()); + }); + } + + @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, 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()); + }, 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, 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()); + }); + } + + @Test + public void testMailtotUri() { + String testUriStr = "mailto:[email protected]"; + + testUri(testUriStr, false, false, false, false, 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()); + }); + } + + @Test + public void testHashOnlyUri() { + + testUri("#", 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()); + }); + + testUri("#fragment", 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()); + }); + } + + @Test + public void testQueryOnlyUri() { + + testUri("?", 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()); + }); + + testUri("?test=test", 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()); + }); + } + + @Test + public void testBalanceResourcePathSimpleCases() { + // simple case + String testUriStrSimple = "/test/to/file"; + when(resourceResolver.getResource("/test/to/file")).thenReturn(resource); + testUri(testUriStrSimple, true, true, false, false, resourceUri -> { + assertEquals("/test/to/file", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + }); + + // simple file case + String testUriStrSimpleFile = "/test/to/file.css"; + when(resourceResolver.getResource("/test/to/file.css")).thenReturn(resource); + when(resourceResolver.getResource("/test/to/file")).thenReturn(null); + testUri(testUriStrSimpleFile, true, true, false, false, resourceUri -> { + assertEquals("/test/to/file.css", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + }); + + // simple html rendering case + String testUriStrSimplePage = "/path/to/page.html"; + when(resourceResolver.getResource("/path/to/page.html")).thenReturn(null); + when(resourceResolver.getResource("/path/to/page")).thenReturn(resource); + testUri(testUriStrSimplePage, true, true, false, false, resourceUri -> { + assertEquals("/path/to/page", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals("html", resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + }); + } + + @Test + public void testBalanceResourcePathWithSelectorsAndExtension() { + + String testUriStr = "/test/to/file.ext.sel1.json/suffix/path.js"; + + // pull path with suffix is resource path + when(resourceResolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(resource); + when(resourceResolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext.sel1")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file")).thenReturn(null); + testUri(testUriStr, true, true, 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()); + }); + + // full path without suffix is resource path + when(resourceResolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(resource); + when(resourceResolver.getResource("/test/to/file.ext.sel1")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file")).thenReturn(null); + testUri(testUriStr, true, true, false, false, resourceUri -> { + assertEquals("/test/to/file.ext.sel1.json", resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals("/suffix/path.js", resourceUri.getSuffix()); + }); + + // mix of extension and resource path with dots + when(resourceResolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext.sel1")).thenReturn(resource); + when(resourceResolver.getResource("/test/to/file.ext")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file")).thenReturn(null); + testUri(testUriStr, true, true, 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()); + }); + + when(resourceResolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext.sel1")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext")).thenReturn(resource); + when(resourceResolver.getResource("/test/to/file")).thenReturn(null); + testUri(testUriStr, true, true, false, false, resourceUri -> { + assertEquals("/test/to/file.ext", resourceUri.getResourcePath()); + assertEquals("sel1", resourceUri.getSelectorString()); + assertEquals("json", resourceUri.getExtension()); + assertEquals("/suffix/path.js", resourceUri.getSuffix()); + }); + + // usual case: resource path does not contain dot + when(resourceResolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext.sel1")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file")).thenReturn(resource); + testUri(testUriStr, true, true, false, false, resourceUri -> { + assertEquals("/test/to/file", resourceUri.getResourcePath()); + assertEquals("ext.sel1", resourceUri.getSelectorString()); + assertEquals("json", resourceUri.getExtension()); + assertEquals("/suffix/path.js", resourceUri.getSuffix()); + }); + + // side by side resources in same folder: the longest path wins + when(resourceResolver.getResource("/test/to/file.ext.sel1.json/suffix/path.js")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext.sel1.json")).thenReturn(null); + when(resourceResolver.getResource("/test/to/file.ext.sel1")).thenReturn(resource); + when(resourceResolver.getResource("/test/to/file.ext")).thenReturn(resource); + when(resourceResolver.getResource("/test/to/file")).thenReturn(resource); + testUri(testUriStr, true, true, 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()); + }); + } + + @Test + public void testUnusualQueryFragmentCombinations() { + testUri("?#", false, false, false, false, resourceUri -> { + assertEquals("", resourceUri.getQuery()); + assertEquals("", resourceUri.getFragment()); + }); + testUri("?t=2#", false, false, false, false, resourceUri -> { + assertEquals("t=2", resourceUri.getQuery()); + assertEquals("", resourceUri.getFragment()); + }); + testUri("?#t=3", false, false, false, false, resourceUri -> { + assertEquals("", resourceUri.getQuery()); + assertEquals("t=3", resourceUri.getFragment()); + }); + testUri("", false, false, false, false, resourceUri -> { + assertEquals(null, resourceUri.getQuery()); + assertEquals(null, resourceUri.getFragment()); + }); + } + + // -- adjustment test cases + + @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 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 testAdjustPathInSpecialUriWithoutEffect() { + + testAdjustUri( + "mailto:[email protected]", + resourceUriBuilder -> { + resourceUriBuilder.setPath("/path/to/resource"); + resourceUriBuilder.setResourcePath("/path/to/resource"); + resourceUriBuilder.addSelector("test"); + resourceUriBuilder.setExtension("html"); + resourceUriBuilder.setSuffix("/suffix"); + }, + "mailto:[email protected]", + resourceUri -> { + assertEquals(null, resourceUri.getResourcePath()); + assertEquals(null, resourceUri.getSelectorString()); + assertEquals(null, resourceUri.getExtension()); + assertEquals(null, resourceUri.getSuffix()); + }); + } + + @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()); + }); + } + + // -- helper methods + + + public ResourceUri testUri(String testUri, boolean isPath, boolean isAbsolutePath, boolean isRelativePath, boolean isFullUri, + Consumer<ResourceUri> additionalAssertions) { + return testUri(testUri, isPath, isAbsolutePath, isRelativePath, isFullUri, additionalAssertions, false); + } + + public ResourceUri testUri(String testUri, boolean isPath, boolean isAbsolutePath, boolean isRelativePath, boolean isFullUri, + Consumer<ResourceUri> additionalAssertions, boolean urlIsRestructured) { + ResourceUri resourceUri = ResourceUriBuilder.parse(testUri, resourceResolver).build(); + + if (!urlIsRestructured) { + assertEquals(testUri, resourceUri.toString()); + assertEquals(testUri, resourceUri.toUri().toString()); + } + + assertEquals("isPath()", isPath, resourceUri.isPath()); + assertEquals("isAbsolutePath()", isAbsolutePath, resourceUri.isAbsolutePath()); + assertEquals("isRelativePath()", isRelativePath, resourceUri.isRelativePath()); + assertEquals("isFullUri()", isFullUri, resourceUri.isFullUri()); + + additionalAssertions.accept(resourceUri); + + return resourceUri; + } + + public void testAdjustUri(String testUri, Consumer<ResourceUriBuilder> adjuster, String testUriAfterEdit, + Consumer<ResourceUri> additionalAssertions) { + 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/ResourceUriToSlingRequestPathInfoCompatibilityTest.java b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriToSlingRequestPathInfoCompatibilityTest.java new file mode 100644 index 0000000..b452be4 --- /dev/null +++ b/src/test/java/org/apache/sling/api/resource/uri/ResourceUriToSlingRequestPathInfoCompatibilityTest.java @@ -0,0 +1,271 @@ +/* + * 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.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@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 + @Ignore + 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 + @Ignore + 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()); + } + + @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()); + } + +}
