All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.composum.sling.core.util.SlingResourceUtil Maven / Gradle / Ivy

There is a newer version: 4.3.4
Show newest version
package com.composum.sling.core.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import groovyjarjarantlr4.v4.runtime.misc.NotNull;

/**
 * A set of utility functions related to the handling of Sling Resources, without going down to JCR specifics.
 * This is a replacement for the JCR usage ridden {@link PropertyUtil} that contains all "pure" Sling functions that do not
 * require the Resources to be JCR resources.
 */
public class SlingResourceUtil {

    /**
     * Returns the shortest relative path that leads from a node to another node.
     * Postcondition: {@code ResourceUtil.normalize(node + "/" + result), is(ResourceUtil.normalize(other))}.
     * In most cases  {@link #appendPaths(String, String)}(node, result) will return {other}. )}
     * 

* Examples: relativePath("/foo", "/foo/bar") = "bar" ; relativePath("/foo/bar", "/foo") = ".." ; * relativePath("foo", "foo") = "../bar" . * * @param node the parent * @param other a path of a child of parent / parent itself * @return the (relative) path from which other can be read from node - e.g. with * {@link org.apache.sling.api.resource.Resource#getChild(String)}. If node and other are the same, this is empty. * @throws IllegalArgumentException in those cases where there is no sensible answer: one of the paths is empty or one absolute and one relative path */ public static String relativePath(@NotNull String node, @NotNull String other) { node = ResourceUtil.normalize(node); other = ResourceUtil.normalize(other); //noinspection IfStatementWithTooManyBranches,OverlyComplexBooleanExpression if (StringUtils.isBlank(node) || StringUtils.isBlank(other) || (StringUtils.startsWith(node, "/") && !StringUtils.startsWith(other, "/")) || (!StringUtils.startsWith(node, "/") && StringUtils.startsWith(other, "/")) ) { // no sensible answer here throw new IllegalArgumentException("Invalid path: node=" + node + " , other=" + other); } else if (node.equals(other)) { return ""; } else if (other.startsWith(node + '/')) { return other.substring(node.length() + 1); } else { if (!node.endsWith("/")) { node = node + "/"; } if (other.startsWith(node)) { return other.substring(node.length()); } else { String longestPrefix = StringUtils.getCommonPrefix(node, other); if (!longestPrefix.endsWith("/")) { longestPrefix = StringUtils.defaultString(ResourceUtil.getParent(longestPrefix)); } String nodeRestpath = other.substring(longestPrefix.length()); String otherRestpath = node.substring(longestPrefix.length()); return StringUtils.repeat("../", StringUtils.countMatches(otherRestpath, "/")) + nodeRestpath; } } } /** * Checks whether {descendant} is the same path as parent node or a path of a descendant of the parent node. (We don't check * whether the resources exist - just check the paths. * * @param parent the parent or null * @param descendant the descendant or null * @return true if descendant is a descendant of parent , false if any is null. */ public static boolean isSameOrDescendant(@Nullable String parent, @Nullable String descendant) { if (parent == null || descendant == null) { return false; } if (parent.equals(descendant) || parent.equals("/")) { return true; } if (descendant.startsWith(parent + '/') && !descendant.contains("..")) { return true; } String parentNormalized = ResourceUtil.normalize(parent); String descendantNormalized = ResourceUtil.normalize(descendant); return parentNormalized.equals(descendantNormalized) || parentNormalized.equals("/") || descendantNormalized.startsWith(parentNormalized + '/'); } /** * Checks whether {descendant} is the same path as parent node or a path of a descendant of the parent node. * * @param parent the parent or null * @param descendant the descendant or null * @return true if descendant is a descendant of parent , false if any is null. */ public static boolean isSameOrDescendant(@Nullable Resource parent, @Nullable Resource descendant) { if (parent == null || descendant == null) { return false; } return isSameOrDescendant(parent.getPath(), descendant.getPath()); } /** * Returns the path of a resource, or null if it is null. For use e.g. in logging statements. */ @Nullable public static String getPath(@Nullable Resource resource) { return resource != null ? resource.getPath() : null; } /** * Returns the list of paths of a number of resources. For use e.g. in logging statements. */ @NotNull public static List getPaths(@Nullable List resources) { List paths = new ArrayList<>(); if (resources != null) { for (Resource resource : resources) { paths.add(getPath(resource)); } } return paths; } /** * Adds a mixin if it isn't there already. * * @return true if we needed to add the mixin. */ public static boolean addMixin(@NotNull Resource resource, @NotNull String mixin) { if (!ResourceUtil.isResourceType(resource, mixin)) { ModifiableValueMap vm = resource.adaptTo(ModifiableValueMap.class); String[] mixins = vm.get(ResourceUtil.PROP_MIXINTYPES, new String[0]); List newMixins = new ArrayList<>(Arrays.asList(mixins)); newMixins.add(mixin); vm.put(ResourceUtil.PROP_MIXINTYPES, newMixins.toArray(new String[0])); return true; } return false; } /** * Returns an iterator that goes through all descendants of a resource, parents come before their children. * * @param resource a resource or null * @return an iterable running through the resource and it's descendants, not null */ @NotNull public static Iterable descendants(@Nullable final Resource resource) { return () -> descendantsStream(resource).iterator(); } /** * Returns a stream that goes through all descendants of a resource, parents come before their children. * * @param resource a resource or null * @return a stream running through the resource and it's the descendants, not null */ @NotNull public static Stream descendantsStream(@Nullable Resource resource) { return descendantsStream(resource, null); } /** * Returns a stream that goes through all descendants of a resource until a filter is hit, parents come before * their children. * * @param resource a resource or null * @param leafFilter if this returns true, this assumes the resource is a leaf and does not return it's descendants. * @return a stream running through the resource and it's the descendants, not null */ @Nonnull public static Stream descendantsStream(@Nullable Resource resource, @Nullable Function leafFilter) { if (resource == null) { return Stream.empty(); } if (leafFilter != null && Boolean.TRUE.equals(leafFilter.apply(resource))) { return Stream.of(resource); } return Stream.concat(Stream.of(resource), StreamSupport.stream(resource.getChildren().spliterator(), false) .flatMap((r) -> SlingResourceUtil.descendantsStream(r, leafFilter))); } /** * Returns a stream of the resource and all its parents. */ @NotNull public static Stream selfAndAncestors(@Nullable Resource r) { if (r == null) { return Stream.empty(); } // yes, that should be done with takeWhile, but we are restricted to Java 8 here for now. return Stream.iterate(r, Resource::getParent) .limit(StringUtils.countMatches(r.getPath(), "/")) .filter(Objects::nonNull); } /** * Appends two paths: determines the given child of a path. * * @param path an absolute or relative path. We ignore if it ends with /. If it's null, we return null * (there is no child of no path). * @param childpath the relative path of the child to the path. Absolute paths are treated as relative paths: we * ignore starting and ending / . if empty or null we return path * @return the paths concatenated. If path is absolute, this is absolute; if path is relative, this is relative. */ @Nullable public static String appendPaths(@Nullable String path, @Nullable String childpath) { if (StringUtils.isBlank(path)) { return null; } if (StringUtils.isBlank(childpath)) { return path; } childpath = StringUtils.removeStart(childpath, "/"); childpath = StringUtils.removeEnd(childpath, "/"); if (StringUtils.isBlank(childpath)) { return path; } if ("/".equals(ResourceUtil.normalize(path))) { return "/" + childpath; } path = StringUtils.removeEnd(path, "/"); return path + "/" + childpath; } /** * Finds the longest path that is a parent of all given paths - e.g. /foo/bar for /foo/bar/a/b, /foo/bar/a/c and * /foo/bar/b/c . * * @param paths a collection of absolute paths or a collection of relative paths. Null / empty values are ignored. * @return null if collection is empty, otherwise the common parent. It might be an empty string if the paths * have no common parent (e.g. "a" and "b" or the illegitimate call with "/a" and "b"). */ @Nullable public static String commonParent(@Nullable Collection paths) { if (paths == null || paths.isEmpty()) { return null; } String result = null; for (String path : paths) { if (StringUtils.isBlank(path)) { continue; } if (result == null) { result = path; } else { while (!isSameOrDescendant(result, path) && result != null) { result = ResourceUtil.getParent(result); } if (result == null) { break; } // no common parents } } return result; } /** * Retrieves the first parent (including path itself) that actually exists. * * @param path an absolute path * @return path or the longest path parent to path that exists in resolver. Null if path * is empty. */ @Nullable public static Resource getFirstExistingParent(@Nullable ResourceResolver resolver, @Nullable String path) { if (resolver == null) { return null; } String searchedPath = path; Resource result = StringUtils.isNotBlank(searchedPath) ? resolver.getResource(searchedPath) : null; while (result == null && StringUtils.isNotBlank(searchedPath)) { searchedPath = ResourceUtil.getParent(searchedPath); result = resolver.getResource(searchedPath); } return result; } /** * Adds one or more mixins to the resource, keeping the current ones. */ public static void addMixin(@Nullable Resource resource, @Nullable String... mixins) { if (resource != null && mixins != null && mixins.length > 0) { ModifiableValueMap mvm = resource.adaptTo(ModifiableValueMap.class); String[] currentMixins = mvm.get(ResourceUtil.PROP_MIXINTYPES, String[].class); if (currentMixins == null) { mvm.put(ResourceUtil.PROP_MIXINTYPES, mixins); } else { List allMixins = new ArrayList<>(); allMixins.addAll(Arrays.asList(currentMixins)); for (String mixin : mixins) { if (!allMixins.contains(mixin)) { allMixins.add(mixin); } } mvm.put(ResourceUtil.PROP_MIXINTYPES, allMixins.toArray(new String[allMixins.size()])); } } } /** * Sets a property on a {@link ModifiableValueMap}. This abbreviates the procedure if it's not known whether * the value is null - {@link ModifiableValueMap#put(Object, Object)} throws up if it is. */ public static void setProperty(@NotNull ModifiableValueMap valueMap, @NotNull String key, @Nullable Object value) { if (value == null) { valueMap.remove(key); } else { valueMap.put(key, value); } } /** * Returns the children of a resource as {@link Stream}. * * @param a resource or null * @return stream with the children, possibly empty. */ @Nonnull public static Stream getChildrenAsStream(@Nullable Resource resource) { if (resource == null) { return Stream.empty(); } return StreamSupport.stream(resource.getChildren().spliterator(), false); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy