You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@sling.apache.org by GitBox <gi...@apache.org> on 2020/09/18 09:21:34 UTC

[GitHub] [sling-org-apache-sling-api] ghenzler commented on a change in pull request #23: SLING-9745 Introduce ResourceUri (immutable) and ResourceUriBuilder

ghenzler commented on a change in pull request #23:
URL: https://github.com/apache/sling-org-apache-sling-api/pull/23#discussion_r490816967



##########
File path: src/main/java/org/apache/sling/api/resource/uri/ResourceUriBuilder.java
##########
@@ -0,0 +1,843 @@
+/*
+ * 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);
+        if (path == null) {
+            return this; // nothing to rebase
+        }
+        ResourcePathIterator it = new ResourcePathIterator(path);
+        String availableResourcePath = null;
+        while (it.hasNext()) {
+            availableResourcePath = it.next();
+            if (resourceResolver.getResource(availableResourcePath) != null) {
+                break;
+            }
+        }
+        if (availableResourcePath == null) {
+            return this; // nothing to rebase
+        }
+
+        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.

Review comment:
       very good point @kwin - so I'll keep a member var `isBuilt` and throw an exception if a builder instance is used twice to build an uri




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org