ComponentPropertyResolver.java
- /*
- * #%L
- * wcm.io
- * %%
- * Copyright (C) 2019 wcm.io
- * %%
- * Licensed 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.
- * #L%
- */
- package io.wcm.wcm.commons.component;
- import java.util.Collection;
- import java.util.Map;
- import org.apache.commons.collections4.IterableUtils;
- import org.apache.commons.lang3.StringUtils;
- import org.apache.sling.api.resource.LoginException;
- import org.apache.sling.api.resource.Resource;
- import org.apache.sling.api.resource.ResourceResolver;
- import org.apache.sling.api.resource.ResourceResolverFactory;
- import org.apache.sling.api.resource.ResourceUtil;
- import org.jetbrains.annotations.NotNull;
- import org.jetbrains.annotations.Nullable;
- import org.osgi.annotation.versioning.ProviderType;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import com.day.cq.wcm.api.Page;
- import com.day.cq.wcm.api.PageManager;
- import com.day.cq.wcm.api.components.Component;
- import com.day.cq.wcm.api.components.ComponentContext;
- import com.day.cq.wcm.api.components.ComponentManager;
- import com.day.cq.wcm.api.policies.ContentPolicy;
- import com.day.cq.wcm.api.policies.ContentPolicyManager;
- import io.wcm.sling.commons.adapter.AdaptTo;
- /**
- * Tries to resolve properties with or without inheritance from pages, content policies or component definitions.
- * <p>
- * The lookup can take place in:
- * </p>
- * <ol>
- * <li>Properties of the current page (including the parent pages if inheritance is enabled)</li>
- * <li>Properties from the content policy associated with the current resource</li>
- * <li>Properties defined on the component associated with the current resource (including super components if
- * inheritance is enabled)</li>
- * </ol>
- * <p>
- * By default, only option 3 is enabled (with inheritance).
- * Please make sure to {@link #close()} instances of this class after usage.
- * </p>
- */
- @ProviderType
- public final class ComponentPropertyResolver implements AutoCloseable {
- private ComponentPropertyResolution componentPropertiesResolution = ComponentPropertyResolution.RESOLVE_INHERIT;
- private ComponentPropertyResolution pagePropertiesResolution = ComponentPropertyResolution.IGNORE;
- private ComponentPropertyResolution contentPolicyResolution = ComponentPropertyResolution.IGNORE;
- private final Page currentPage;
- private final Component currentComponent;
- private final Resource resource;
- private final ResourceResolverFactory resourceResolverFactory;
- private ResourceResolver componentsResourceResolver;
- private boolean initComponentsResourceResolverFailed;
- private static final String SERVICEUSER_SUBSERVICE = "component-properties";
- private static final Logger log = LoggerFactory.getLogger(ComponentPropertyResolver.class);
- /**
- * This constructor is for internal use only, please use {@link ComponentPropertyResolverFactory}.
- * @param page Content page
- * @param resourceResolverFactory Resource resolver factory
- */
- public ComponentPropertyResolver(@NotNull Page page,
- @Nullable ResourceResolverFactory resourceResolverFactory) {
- this(page.getContentResource(), resourceResolverFactory);
- }
- /**
- * This constructor is for internal use only, please use {@link ComponentPropertyResolverFactory}.
- * @param resource Content resource
- * @param resourceResolverFactory Resource resolver factory
- */
- public ComponentPropertyResolver(@NotNull Resource resource,
- @Nullable ResourceResolverFactory resourceResolverFactory) {
- this(resource, false, resourceResolverFactory);
- }
- /**
- * This constructor is for internal use only, please use {@link ComponentPropertyResolverFactory}.
- * @param resource Content resource
- * @param ensureResourceType Ensure the given resource has a resource type.
- * If this is not the case, try to find the closest parent resource which has a resource type.
- * @param resourceResolverFactory Resource resolver factory
- */
- public ComponentPropertyResolver(@NotNull Resource resource, boolean ensureResourceType,
- @Nullable ResourceResolverFactory resourceResolverFactory) {
- Resource contextResource = null;
- if (ensureResourceType) {
- // find closest parent resource that has a resource type (and not nt:unstructured)
- contextResource = getResourceWithResourceType(resource);
- }
- if (contextResource == null) {
- contextResource = resource;
- }
- ResourceResolver resourceResolver = contextResource.getResourceResolver();
- PageManager pageManager = AdaptTo.notNull(resourceResolver, PageManager.class);
- this.currentPage = pageManager.getContainingPage(contextResource);
- if (hasResourceType(contextResource)) {
- ComponentManager componentManager = AdaptTo.notNull(resourceResolver, ComponentManager.class);
- this.currentComponent = componentManager.getComponentOfResource(contextResource);
- }
- else {
- this.currentComponent = null;
- }
- this.resource = contextResource;
- this.resourceResolverFactory = resourceResolverFactory;
- }
- /**
- * This constructor is for internal use only, please use {@link ComponentPropertyResolverFactory}.
- * @param wcmComponentContext WCM component context
- * @param resourceResolverFactory Resource resolver factory
- */
- public ComponentPropertyResolver(@NotNull ComponentContext wcmComponentContext,
- @Nullable ResourceResolverFactory resourceResolverFactory) {
- this.currentPage = wcmComponentContext.getPage();
- this.currentComponent = wcmComponentContext.getComponent();
- this.resource = wcmComponentContext.getResource();
- this.resourceResolverFactory = resourceResolverFactory;
- }
- /**
- * Lookup for content resource associated with the page component (resource type).
- * @param page Content page
- * @deprecated Please use {@link ComponentPropertyResolverFactory}.
- */
- @Deprecated(since = "1.6.0")
- public ComponentPropertyResolver(@NotNull Page page) {
- this(page, null);
- }
- /**
- * Lookup for content resource associated with a component (resource type).
- * @param resource Content resource
- * @deprecated Please use {@link ComponentPropertyResolverFactory}.
- */
- @Deprecated(since = "1.6.0")
- public ComponentPropertyResolver(@NotNull Resource resource) {
- this(resource, null);
- }
- /**
- * Lookup for content resource associated with a component (resource type).
- * @param resource Content resource
- * @param ensureResourceType Ensure the given resource has a resource type.
- * If this is not the case, try to find the closest parent resource which has a resource type.
- * @deprecated Please use {@link ComponentPropertyResolverFactory}.
- */
- @Deprecated(since = "1.6.0")
- public ComponentPropertyResolver(@NotNull Resource resource, boolean ensureResourceType) {
- this(resource, ensureResourceType, null);
- }
- /**
- * Lookup with content resource associated with a component (resource type).
- * @param wcmComponentContext WCM component context
- * @deprecated Please use {@link ComponentPropertyResolverFactory}.
- */
- @Deprecated(since = "1.6.0")
- public ComponentPropertyResolver(@NotNull ComponentContext wcmComponentContext) {
- this(wcmComponentContext, null);
- }
- private static boolean hasResourceType(@NotNull Resource resource) {
- return StringUtils.isNotEmpty(resource.getResourceType());
- }
- @SuppressWarnings({ "null", "java:S2589" }) // extra null checks for backward compatibility
- private static @Nullable Resource getResourceWithResourceType(@Nullable Resource resource) {
- if (resource == null) {
- return null;
- }
- String resourceType = resource.getResourceType();
- if (resourceType != null && isPathBasedResourceType(resourceType)) {
- return resource;
- }
- return getResourceWithResourceType(resource.getParent());
- }
- /**
- * Checks if the resource type is a path pointing to a component resource in the repository
- * (where we can lookup properties to inherit from).
- * @param resourceType Resource type
- * @return true for path-based resource types
- */
- private static boolean isPathBasedResourceType(@NotNull String resourceType) {
- return StringUtils.contains(resourceType, "/");
- }
- /**
- * Configure if properties should be resolved in component properties, and with or without inheritance.
- * Default mode is {@link ComponentPropertyResolution#RESOLVE_INHERIT}.
- * @param resolution Resolution mode
- * @return this
- */
- public ComponentPropertyResolver componentPropertiesResolution(@NotNull ComponentPropertyResolution resolution) {
- this.componentPropertiesResolution = resolution;
- return this;
- }
- /**
- * Configure if properties should be resolved in content page properties, and with or without inheritance.
- * Default mode is {@link ComponentPropertyResolution#IGNORE}.
- * @param resolution Resolution mode
- * @return this
- */
- public ComponentPropertyResolver pagePropertiesResolution(@NotNull ComponentPropertyResolution resolution) {
- this.pagePropertiesResolution = resolution;
- return this;
- }
- /**
- * Configure if properties should be resolved from content policies mapped for the given resource.
- * No explicit inheritance mode is supported, so {@link ComponentPropertyResolution#RESOLVE_INHERIT}
- * has the same effect as {@link ComponentPropertyResolution#RESOLVE} in this case.
- * Default mode is {@link ComponentPropertyResolution#IGNORE}.
- * @param resolution Resolution mode
- * @return this
- */
- public ComponentPropertyResolver contentPolicyResolution(@NotNull ComponentPropertyResolution resolution) {
- this.contentPolicyResolution = resolution;
- return this;
- }
- /**
- * Get property.
- * @param name Property name
- * @param type Property type
- * @param <T> Parameter type
- * @return Property value or null if not set
- */
- public @Nullable <T> T get(@NotNull String name, @NotNull Class<T> type) {
- @Nullable
- T value = getPageProperty(currentPage, name, type);
- if (value == null) {
- value = getContentPolicyProperty(name, type);
- }
- if (value == null) {
- value = getComponentProperty(currentComponent, name, type);
- }
- return value;
- }
- /**
- * Get property.
- * @param name Property name
- * @param defaultValue Default value
- * @param <T> Parameter type
- * @return Property value or default value if not set
- */
- public @NotNull <T> T get(@NotNull String name, @NotNull T defaultValue) {
- @Nullable
- @SuppressWarnings("unchecked")
- T value = get(name, (Class<T>)defaultValue.getClass());
- if (value != null) {
- return value;
- }
- else {
- return defaultValue;
- }
- }
- private @Nullable <T> T getComponentProperty(@Nullable Component component,
- @NotNull String name, @NotNull Class<T> type) {
- if (componentPropertiesResolution == ComponentPropertyResolution.IGNORE || component == null) {
- return null;
- }
- @Nullable
- T result;
- if (StringUtils.contains(name, "/")) {
- // if a property in child resource is addressed get property value via local resource
- // because the map behind the getProperties() method does not support child resource access
- String childResourcePath = StringUtils.substringBeforeLast(name, "/");
- String localPropertyName = StringUtils.substringAfterLast(name, "/");
- Resource childResource = getLocalComponentResource(component, childResourcePath);
- result = ResourceUtil.getValueMap(childResource).get(localPropertyName, type);
- }
- else {
- result = component.getProperties().get(name, type);
- }
- if (result == null && componentPropertiesResolution == ComponentPropertyResolution.RESOLVE_INHERIT) {
- result = getComponentProperty(component.getSuperComponent(), name, type);
- }
- return result;
- }
- private @Nullable <T> T getPageProperty(@Nullable Page page,
- @NotNull String name, @NotNull Class<T> type) {
- if (pagePropertiesResolution == ComponentPropertyResolution.IGNORE || page == null) {
- return null;
- }
- @Nullable
- T result = page.getProperties().get(name, type);
- if (result == null && pagePropertiesResolution == ComponentPropertyResolution.RESOLVE_INHERIT) {
- result = getPageProperty(page.getParent(), name, type);
- }
- return result;
- }
- private @Nullable <T> T getContentPolicyProperty(@NotNull String name, @NotNull Class<T> type) {
- if (contentPolicyResolution == ComponentPropertyResolution.IGNORE || resource == null) {
- return null;
- }
- ContentPolicy policy = getPolicy(resource);
- if (policy != null) {
- return policy.getProperties().get(name, type);
- }
- return null;
- }
- /**
- * Get list of child resources.
- * @param name Child node name
- * @return List of child resources or null if not set.
- */
- public @Nullable Collection<Resource> getResources(@NotNull String name) {
- Collection<Resource> list = getPageResources(currentPage, name);
- if (list == null) {
- list = getContentPolicyResources(name);
- }
- if (list == null) {
- list = getComponentResources(currentComponent, name);
- }
- return list;
- }
- private @Nullable Collection<Resource> getComponentResources(@Nullable Component component, @NotNull String name) {
- if (componentPropertiesResolution == ComponentPropertyResolution.IGNORE || component == null) {
- return null;
- }
- Collection<Resource> result = getResources(getLocalComponentResource(component, name));
- if (result == null && componentPropertiesResolution == ComponentPropertyResolution.RESOLVE_INHERIT) {
- result = getComponentResources(component.getSuperComponent(), name);
- }
- return result;
- }
- private @Nullable Collection<Resource> getPageResources(@Nullable Page page, @NotNull String name) {
- if (pagePropertiesResolution == ComponentPropertyResolution.IGNORE || page == null) {
- return null;
- }
- Collection<Resource> result = getResources(page.getContentResource(name));
- if (result == null && pagePropertiesResolution == ComponentPropertyResolution.RESOLVE_INHERIT) {
- result = getPageResources(page.getParent(), name);
- }
- return result;
- }
- @SuppressWarnings("PMD.ReturnEmptyCollectionRatherThanNull")
- private @Nullable Collection<Resource> getContentPolicyResources(@NotNull String name) {
- if (contentPolicyResolution == ComponentPropertyResolution.IGNORE || resource == null) {
- return null;
- }
- ContentPolicy policy = getPolicy(resource);
- if (policy != null) {
- Resource policyResource = policy.adaptTo(Resource.class);
- if (policyResource != null) {
- return getResources(policyResource.getChild(name));
- }
- }
- return null;
- }
- private @Nullable Collection<Resource> getResources(@Nullable Resource parent) {
- if (parent == null) {
- return null;
- }
- Collection<Resource> children = IterableUtils.toList(parent.getChildren());
- if (children.isEmpty()) {
- return null;
- }
- return children;
- }
- /**
- * Get content policy via policy manager.
- * @param resource Content resource
- * @return Policy or null
- */
- private static @Nullable ContentPolicy getPolicy(@NotNull Resource resource) {
- ContentPolicyManager policyManager = AdaptTo.notNull(resource.getResourceResolver(), ContentPolicyManager.class);
- return policyManager.getPolicy(resource);
- }
- /**
- * Get local child resource for component, with a special handling for publish environments where
- * the local child resources for components below /apps are not accessible for everyone.
- * @param component Component
- * @param childResourcePath Child resource path
- * @return Resource or null
- */
- private @Nullable Resource getLocalComponentResource(@NotNull Component component,
- @NotNull String childResourcePath) {
- if (componentsResourceResolver == null
- && resourceResolverFactory != null
- && !initComponentsResourceResolverFailed) {
- try {
- componentsResourceResolver = resourceResolverFactory.getServiceResourceResolver(
- Map.of(ResourceResolverFactory.SUBSERVICE, SERVICEUSER_SUBSERVICE));
- }
- catch (LoginException ex) {
- initComponentsResourceResolverFailed = true;
- if (log.isDebugEnabled()) {
- log.debug("Unable to get resource resolver for accessing local component resource, "
- + "please make sure to grant access to system user 'sling-scripting' for "
- + "bundle 'io.wcm.wcm.commons', subservice '{}'.", SERVICEUSER_SUBSERVICE, ex);
- }
- }
- }
- if (componentsResourceResolver != null) {
- String resourcePath = component.getPath() + "/" + childResourcePath;
- return componentsResourceResolver.getResource(resourcePath);
- }
- // fallback implementation for previous behavior - this will usually not work in publish instances
- return component.getLocalResource(childResourcePath);
- }
- @Override
- public void close() {
- if (componentsResourceResolver != null) {
- componentsResourceResolver.close();
- }
- }
- }