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

com.composum.sling.core.InheritedValues Maven / Gradle / Ivy

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

import com.composum.sling.core.util.PropertyUtil;
import com.composum.sling.core.util.ResourceUtil;
import org.apache.commons.lang3.Validate;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;

import static org.apache.commons.lang3.StringUtils.isNotBlank;

/**
 * The value map which extends the property retrieval to the context (parents,...) of a resource.
 */
public class InheritedValues extends HashMap implements ValueMap {

    /**
     * the various types of inheritance
     * 
*
contentRelated
*
* Each node at the same position relative to the 'jcr:content' nodes is checked for an inherited value. *
     *     - jcr:content/aaa/bbb/{relative/property/path}
     *       - ...
     *       - jcr:content/aaa/bbb/{relative/property/path}
     * 
*
contentBased
*
* The 'jcr:content' parents are the base to retrieve the inherited value. *
     *     - jcr:content/{relative/property/path}
     *       - ...
     *       - jcr:content/aaa/.../xxx/{relative/property/path}
     * 
*
nodeRelated
*
* Each parent node can store an appropriate value. *
     *     - jcr:content/{relative/property/path}
     *       - aaa
     *         - bbb/{relative/property/path}
     *           ...
     *           - xxx/{relative/property/path}
     * 
* Can be restricted to the subtree of 'jcr:content/...'.
*
sameContent
*
* The same as 'nodeRelated' but the search is stopped at the next 'jcr:content' parent node if present. *
*
*/ public enum Type { contentRelated, contentBased, nodeRelated, sameContent, /** * Special value to signify that a (context dependent) "default" inheritance type is to be used, for usage in * annotation attributes to specify defaults, since a null value cannot be used there. Only use this in * annotations, not in other code .Context-dependent; it is a runtime error if this is actually used in * ResourceHandle. */ useDefault } public static final Object UNDEFINED = ""; protected final Resource resource; protected final Type inheritanceType; protected transient Resource exitPoint; protected transient Resource entryPoint; protected transient String relativePath; public InheritedValues(Resource resource) { this(resource, Type.contentRelated); } public InheritedValues(Resource resource, Type inheritanceType) { Validate.notNull(inheritanceType, "Inheritance type null is not permitted."); Validate.isTrue(Type.useDefault != inheritanceType, "useDefault is not a valid inheritance strategy"); this.resource = resource; this.inheritanceType = inheritanceType; } @Deprecated public InheritedValues(Resource resource, boolean nodeInheritance) { this(resource, Type.sameContent); } @Deprecated public InheritedValues(Resource resource, boolean nodeInheritance, boolean restrictToSameContent) { this(resource, nodeInheritance ? (restrictToSameContent ? Type.sameContent : Type.nodeRelated) : Type.contentRelated); } /** * Gets an inherited value. * * @param name the property name or path * @param the expected type of the value * @return inherited value if existing or null */ @Override @Nullable public T get(@NotNull final String name, @NotNull final Class type) { Object value = get(name); if (value == null) { value = findInherited(name, type); if (value == null) { value = UNDEFINED; } put(name, value); // cache the found value } return value != UNDEFINED ? type.cast(value) : null; } /** * Gets an inherited value. * * @param name the property name or path * @param defaultValue the default value, must not be null * @param the expected type of the value * @return inherited value if existing, otherwise the default value */ @Override @NotNull public T get(@NotNull final String name, @NotNull final T defaultValue) { Class type = PropertyUtil.getType(defaultValue); T value = get(name, type); return value != null ? value : defaultValue; } @SuppressWarnings("unchecked") protected T findInherited(String name, Class type) { HierarchyScanResult found = findOriginAndValue(name, type); return found != null ? (T) found.value : null; } public static class HierarchyScanResult { public final Resource origin; public final Object value; public HierarchyScanResult(Resource origin, Object value) { this.origin = origin; this.value = value; } } /** * Searches the value along the repositories hierarchy by the entry point and path determined before. * * @param name the property name or path * @param type the expected type of the value * @return the value found or null if no such value found * in one of the appropriate parent nodes */ public HierarchyScanResult findOriginAndValue(String name, Class type) { Object value; findEntryPoint(); String path = getRelativePath(name); for (Resource parent = entryPoint; parent != null; parent = parent.getParent()) { ValueMap parentProps = parent.getValueMap(); value = parentProps.get(path, type); if (value != null) { return new HierarchyScanResult(parent, value); } if (exitPoint != null && parent.getPath().equals(exitPoint.getPath())) { break; } } return null; } protected String getRelativePath(String name) { String path = relativePath; if (!path.endsWith("/") && isNotBlank(path)) { path += "/"; } path += name; path = path.replaceAll("^\\./", ""); path = path.replaceAll("/\\./", "/"); path = path.replaceAll("/[^/]+/\\.\\./", "/"); return path; } /** * Retrieves the first parent node for inheritance traversal (that is, the node above jcr:content) * and defines the relativePath of the property wrt. this node. */ protected void findEntryPoint() { if (entryPoint == null) { StringBuilder path = new StringBuilder(); Resource parent = resource; String name; while (parent != null && !ResourceUtil.CONTENT_NODE.equals(name = parent.getName())) { if (inheritanceType != Type.contentBased) { if (path.length() > 0) { path.insert(0, '/'); } path.insert(0, name); } parent = parent.getParent(); } if (inheritanceType != Type.nodeRelated && inheritanceType != Type.sameContent && parent != null) { // the resource is a child of an element with a content subtree ('jcr:content/...') path.insert(0, '/'); path.insert(0, ResourceUtil.CONTENT_NODE); relativePath = path.toString(); entryPoint = parent.getParent(); } else { // node inheritance or no content subtree ('jcr:content/...') detected... relativePath = ""; entryPoint = resource.getParent(); if (inheritanceType == Type.sameContent && parent != null) { exitPoint = parent; } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy