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

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

/*
 * Copyright (c) 2013 IST GmbH Dresden
 * Eisenstuckstraße 10, 01069 Dresden, Germany
 * All rights reserved.
 *
 * Name: ResourceHandle.java
 * Autor: Ralf Wunsch, Mirko Zeibig
 */

package com.composum.sling.core;

import com.composum.sling.core.mapping.MappingRules;
import com.composum.sling.core.util.PropertyUtil;
import com.composum.sling.core.util.ResourceUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.jackrabbit.JcrConstants;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceWrapper;
import org.apache.sling.api.resource.ValueMap;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.annotation.Nonnull;
import javax.jcr.Node;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * the wrapper to enhance the Sling Resource class
 */
public class ResourceHandle extends ResourceWrapper implements JcrResource, Cloneable {

    /**
     * The 'adaptTo' like wrapping helper.
     *
     * @param resource the wrapped resource. We explicitly allow null here to avoid many null checks though many ResourceWrapper methods throw a NPE then,
     *                 but you need to make sure that you check {@link #isValid()} if that's possible.
     * @return the wrapped resource (may be resource itself if it is a ResourceHandle), not null
     */
    @NotNull
    public static ResourceHandle use(@Nullable Resource resource) {
        return resource instanceof ResourceHandle
                ? ((ResourceHandle) resource) : new ResourceHandle(resource);
    }

    /** the universal validation test */
    public static boolean isValid(@Nullable Resource resource) {
        return resource instanceof ResourceHandle
                ? ((ResourceHandle) resource).isValid()
                : resource != null && resource.getResourceResolver().getResource(resource.getPath()) != null;
    }

    // initialized attributes

    protected final Resource resource;
    @NotNull
    protected final ValueMap properties;

    // attributes retrieved on demand

    private transient Boolean valid;
    private transient Node node;
    private transient String path;
    private transient String id;
    private transient String title;

    private transient ResourceHandle contentResource;
    private transient Map inheritedValuesMap;
    protected InheritedValues.Type inheritanceType = InheritedValues.Type.contentRelated;
    protected boolean useNodeInheritance = false;
    protected transient Calendar lastModified;

    /**
     * creates a new wrapper instance.
     */
    protected ResourceHandle(Resource resource) {
        super(resource);
        this.resource = super.getResource();
        this.properties = ResourceUtil.getValueMap(this.resource);
    }

    /**
     * Returns a shallow clone, if you want to modify attributes like {@link #setInheritanceType(InheritedValues.Type)}
     * without affecting the original.
     *
     * @return a cloned ResourceHandle
     */
    @Override
    public ResourceHandle clone() {
        try {
            return (ResourceHandle) super.clone();
        } catch (CloneNotSupportedException e) { // impossible.
            throw new IllegalStateException("Bug: clone should work.", e);
        }
    }

    /**
     * a resource is valid if not 'null' and resolvable
     */
    public boolean isValid() {
        if (valid == null) {
            valid = (this.resource != null);
            if (valid) {
                valid = (getResourceResolver().getResource(getPath()) != null);
            }
        }
        return valid;
    }

    // property access

    public  T getProperty(String key, T defaultValue) {
        Class type = PropertyUtil.getType(defaultValue);
        T value = getProperty(key, type);
        return value != null ? value : defaultValue;
    }

    public  T getProperty(String key, Class type) {
        return properties.get(key, type);
    }

    public String getProperty(String key) {
        return getProperty(key, String.class);
    }

    @NotNull
    public ValueMap getProperties() {
        return properties;
    }

    public void setProperty(String name, String value) throws RepositoryException {
        setProperty(name, value, PropertyType.STRING);
    }

    public void setProperty(String name, boolean value) throws RepositoryException {
        setProperty(name, value, PropertyType.BOOLEAN);
    }

    public void setProperty(String name, Calendar value) throws RepositoryException {
        setProperty(name, value, PropertyType.DATE);
    }

    public void setProperty(String name, Object value, int type)
            throws RepositoryException {
        Node node = getNode();
        if (node != null) {
            PropertyUtil.setProperty(node, name, value, type);
        }
    }

    public void setProperty(String name, InputStream input)
            throws RepositoryException {
        Node node = getNode();
        if (node != null) {
            PropertyUtil.setProperty(node, name, input);
        }
    }

    public void setProperty(String name, Iterable values) throws RepositoryException {
        setProperty(name, values, PropertyType.STRING);
    }

    public void setProperty(String name, Iterable values, int type)
            throws RepositoryException {
        Node node = getNode();
        if (node != null) {
            PropertyUtil.setProperty(node, name, values, type);
        }
    }

    // content resource access ('jcr:content' child resource)

    /**
     * Retrieves the 'content' resource of this resource. Normally the content resource is the resource ot the
     * 'jcr:content' subnode if this resource ist not the content resource itself.
     *
     * @return the content resource if present or self
     */
    public ResourceHandle getContentResource() {
        if (contentResource == null) {
            if (ResourceUtil.CONTENT_NODE.equals(getName()) || !this.isValid()) {
                contentResource = this;
            } else {
                contentResource = ResourceHandle.use(this.getChild(ResourceUtil.CONTENT_NODE));
                if (!contentResource.isValid()) {
                    contentResource = this; // fallback to the resource itself if no content exists
                }
            }
        }
        return contentResource;
    }

    /** Returns a property of the {@link #getContentResource()}. */
    public  T getContentProperty(String key, Class type) {
        return getContentResource().getProperty(key, type);
    }

    /** Returns a property of the {@link #getContentResource()}. */
    public  T getContentProperty(String key, T defaultValue) {
        return getContentResource().getProperty(key, defaultValue);
    }

    // inherited property values

    /**
     * Sets inheritance type for {@link #getInherited(String, Class)} and {@link #getInherited(String, Object)} to
     * {@link InheritedValues.Type#sameContent}.
     *
     * @see #clone()
     * @see #withInheritanceType(InheritedValues.Type)
     * @deprecated please use {@link #withInheritanceType(InheritedValues.Type)} with {@link
     * InheritedValues.Type#sameContent} to keep ResourceHandle effectively immutable.
     */
    @Deprecated
    public void setUseNodeInheritance(boolean nodeInheritance) {
        setInheritanceType(InheritedValues.Type.sameContent);
    }

    /**
     * Sets inheritance type for {@link #getInherited(String, Class)} and {@link #getInherited(String, Object)}.
     *
     * @param type the type
     * @see #clone()
     * @see #withInheritanceType(InheritedValues.Type)
     * @deprecated please prefer {@link #withInheritanceType(InheritedValues.Type)} to keep this effectively immutable
     */
    @Deprecated
    public void setInheritanceType(InheritedValues.Type type) {
        inheritanceType = type;
    }

    /**
     * Returns a {@link ResourceHandle} with the given inheritance type for {@link #getInherited(String, Class)} and
     * {@link #getInherited(String, Object)}.
     *
     * @param type the type
     * @return the resource handle; might be this if the type is unchanged.
     */
    @SuppressWarnings("deprecation")
    public ResourceHandle withInheritanceType(InheritedValues.Type type) {
        Validate.notNull(type, "The inheritance type must not be null");
        if (inheritanceType == type) return this;
        ResourceHandle clone = clone();
        clone.setInheritanceType(type);
        return clone;
    }

    public InheritedValues getInheritedValues() {
        return getInheritedValues(inheritanceType);
    }

    public InheritedValues getInheritedValues(InheritedValues.Type type) {
        if (null == inheritedValuesMap) {
            inheritedValuesMap = new EnumMap<>(InheritedValues.Type.class);
        }
        InheritedValues res = inheritedValuesMap.get(type);
        if (null == res) {
            res = new InheritedValues(this, type);
            inheritedValuesMap.put(type, res);
        }
        return res;
    }

    public  T getInherited(String key, T defaultValue) {
        return getInherited(key, defaultValue, inheritanceType);
    }

    public  T getInherited(String key, Class type) {
        return getInherited(key, type, inheritanceType);
    }

    public  T getInherited(String key, T defaultValue, InheritedValues.Type inheritanceType) {
        Class type = PropertyUtil.getType(defaultValue);
        T value = getInherited(key, type, inheritanceType);
        return value != null ? value : defaultValue;
    }

    public  T getInherited(String key, Class type, InheritedValues.Type inheritanceType) {
        T value = getProperty(key, type);
        if (value == null) {
            value = getInheritedValues(inheritanceType).get(key, type);
        }
        return value;
    }

    /**
     * lazy getter for the node of this resource (if present, not useful for synthetic resources)
     *
     * @return the node object or null if not available
     */
    public Node getNode() {
        if (node == null) {
            if (resource != null) {
                node = resource.adaptTo(Node.class);
            }
        }
        return node;
    }

    /**
     * retrieves the primary type of the resources node; is using the 'getPrimaryType()' method
     * if the wrapped resource is a JcrResource.
     */
    @Override
    public String getPrimaryType() {
        return resource instanceof JcrResource
                ? ((JcrResource) resource).getPrimaryType()
                : getProperty(JcrConstants.JCR_PRIMARYTYPE);
    }

    /**
     * Checks whether any of the resource's primary type, super types, sling resource type and supertypes is {resourceType}.
     */
    public boolean isOfType(String type) {
        return ResourceUtil.isResourceType(getResource(), type);
    }

    /**
     * Lazy getter for the ID of the resources. The ID is the UUID of the resources node if available otherwise the
     * Base64 encoded path.
     *
     * @return a hopefully useful ID (not null)
     */
    public String getId() {
        if (id == null) {
            if (isValid()) {
                id = getProperty(ResourceUtil.PROP_UUID);
            }
            if (StringUtils.isBlank(id)) {
                id = Base64.encodeBase64String(getPath().getBytes(MappingRules.CHARSET));
            }
        }
        return id;
    }

    /**
     * Retrieves the inherited 'super.toString()' value as an ID.
     */
    public String getStringId() {
        return super.toString();
    }

    @Override
    public String getName() {
        return resource != null ? super.getName() : null;
    }

    public String getTitle() {
        if (title == null) {
            title = getProperty(ResourceUtil.PROP_TITLE, String.class);
            if (StringUtils.isBlank(title)) {
                title = getProperty("title", String.class);
            }
            if (StringUtils.isBlank(title)) {
                title = getName();
            }
        }
        return title;
    }

    @Override
    public String getPath() {
        if (path == null) {
            if (resource != null) {
                path = super.getPath();
                if (path.startsWith("//")) { // AEM 6.1 root elements !?
                    path = path.substring(1);
                }
            }
        }
        return path;
    }

    @Override
    public boolean isResourceType(final String resourceType) {
        return resource != null && super.isResourceType(resourceType);
    }

    @Override
    public String getResourceType() {
        return resource != null ? super.getResourceType() : null;
    }

    public String getResourceName() {
        String name = getName();
        return StringUtils.isNotBlank(name) ? name : getPath();
    }

    public String getResourceTitle() {
        String title = getTitle();
        return StringUtils.isNotBlank(title) ? title : getResourceName();
    }

    @Nullable
    @Override
    public ResourceHandle getParent() {
        if (resource != null) {
            Resource parent = super.getParent();
            if (parent == null && isSynthetic()) {
                final String parentPath = getParentPath();
                if (StringUtils.isBlank(parentPath)) { return null; }
                return ResourceHandle.use(getResourceResolver().resolve(parentPath));
            } else if (parent == null) {
                return null;
            } else {
                return ResourceHandle.use(parent);
            }
        } else {
            return null;
        }
    }

    /**
     * Returns the {distance}-th parent - getParent(1) is just getParent().
     */
    @Nullable
    public ResourceHandle getParent(int distance) {
        ResourceHandle parent = this;
        while (distance > 0 && parent != null && parent.isValid()) {
            parent = parent.getParent();
            distance--;
        }
        return parent;
    }

    public String getParentPath() {
        return ResourceUtil.getParent(getPath());
    }

    /**
     * Retrieves a child of this resource or a parent specified by its base path, name pattern and type; for example
     * findUpwards("jcr:content", Pattern.compile("^some.*$"), "sling:Folder").
     */
    public ResourceHandle findUpwards(String basePath, Pattern namePattern, String childType) {
        ResourceHandle current = this;
        while (current != null && current.isValid()) {
            ResourceHandle base = ResourceHandle.use(current.getChild(basePath));
            if (base.isValid()) {
                for (ResourceHandle child : base.getChildrenByType(childType)) {
                    if (namePattern.matcher(child.getName()).matches()) {
                        return child;
                    }
                }
            }
            current = current.getParent();
        }
        return null;
    }


    @Override
    public String toString() {
        return isValid() ? super.toString() : ("");
    }

    /**
     * @see ResourceUtil#isSyntheticResource(Resource)
     */
    public boolean isSynthetic() {
        return ResourceUtil.isSyntheticResource(this);
    }

    /**
     * @see org.apache.sling.api.resource.ResourceWrapper#adaptTo(java.lang.Class)
     */
    @Override
    public  AdapterType adaptTo(Class type) {
        if (type == ResourceHandle.class) {
            return type.cast(this);
        } else {
            return this.resource != null ? super.adaptTo(type) : null;
        }
    }

    /**
     * retrieves all children of a type
     */
    public List getChildrenByType(final String type) {
        final ArrayList children = new ArrayList<>();
        if (this.isValid()) {
            for (final Resource child : this.resource.getChildren()) {
                ResourceHandle handle = ResourceHandle.use(child);
                if (handle.isOfType(type)) {
                    children.add(handle);
                }
            }
        }
        return children;
    }

    /**
     * retrieves all children of a sling:resourceType
     */
    public List getChildrenByResourceType(final String resourceType) {
        final ArrayList children = new ArrayList<>();
        if (this.isValid()) {
            for (final Resource child : this.resource.getChildren()) {
                ResourceHandle handle = ResourceHandle.use(child);
                if (handle.isResourceType(resourceType)) {
                    children.add(handle);
                }
            }
        }
        return children;
    }

    /**
     * Returns 'true' is this resource can be displayed itself.
     */
    public boolean isRenderable() {
        String resourceType = getResourceType();
        return !StringUtils.isBlank(resourceType) || isRenderableFile();
    }

    /**
     * Returns 'true' is this resource represents a 'file' witch can be displayed (a HTML file).
     */
    public boolean isRenderableFile() {
        return ResourceUtil.isRenderableFile(this);
    }

    /**
     * Returns 'true' is this resource represents a 'file' (an asset).
     */
    public boolean isFile() {
        return ResourceUtil.isFile(this);
    }

    public Calendar getLastModified() {
        if (lastModified == null) {
            lastModified = getProperty(ResourceUtil.PROP_LAST_MODIFIED, Calendar.class);
            if (null == lastModified) {
                lastModified = getProperty(ResourceUtil.PROP_CREATED, Calendar.class);
            }
        }
        return lastModified;
    }

    /**
     * Returns the children as immutable list - sometimes better for iteration.
     */
    @Nonnull
    public List getChildrenList() {
        List children = new ArrayList<>();
        Iterable rawChildren = super.getChildren();
        if (rawChildren != null) {
            rawChildren.forEach(children::add);
        }
        return Collections.unmodifiableList(children);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy