/* * 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.caconfig.management.impl; import static org.apache.sling.caconfig.impl.ConfigurationNameConstants.CONFIGS_BUCKET_NAME; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import org.apache.commons.collections.IteratorUtils; import org.apache.commons.collections.ResettableIterator; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.iterators.ListIteratorWrapper; import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.resource.Resource; import org.apache.sling.caconfig.impl.ConfigurationResourceResolverConfig; import org.apache.sling.caconfig.management.ConfigurationCollectionData; import org.apache.sling.caconfig.management.ConfigurationData; import org.apache.sling.caconfig.management.ConfigurationManagementSettings; import org.apache.sling.caconfig.management.ConfigurationManager; import org.apache.sling.caconfig.management.multiplexer.ConfigurationInheritanceStrategyMultiplexer; import org.apache.sling.caconfig.management.multiplexer.ConfigurationMetadataProviderMultiplexer; import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; import org.apache.sling.caconfig.management.multiplexer.ConfigurationPersistenceStrategyMultiplexer; import org.apache.sling.caconfig.management.multiplexer.ConfigurationResourceResolvingStrategyMultiplexer; import org.apache.sling.caconfig.resource.impl.util.ConfigNameUtil; import org.apache.sling.caconfig.resource.impl.util.MapUtil; import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; import org.apache.sling.caconfig.spi.ConfigurationPersistData; import org.apache.sling.caconfig.spi.ConfigurationPersistenceException; import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component(service = ConfigurationManager.class) public class ConfigurationManagerImpl implements ConfigurationManager { @Reference private ConfigurationResourceResolvingStrategyMultiplexer configurationResourceResolvingStrategy; @Reference private ConfigurationMetadataProviderMultiplexer configurationMetadataProvider; @Reference private ConfigurationPersistenceStrategyMultiplexer configurationPersistenceStrategy; @Reference private ConfigurationInheritanceStrategyMultiplexer configurationInheritanceStrategy; @Reference private ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; @Reference private ConfigurationResourceResolverConfig configurationResourceResolverConfig; @Reference private ConfigurationManagementSettings configurationManagementSettings; private static final Logger log = LoggerFactory.getLogger(ConfigurationManagerImpl.class); @SuppressWarnings("unchecked") @Override public ConfigurationData getConfiguration(Resource resource, String configName) { ConfigNameUtil.ensureValidConfigName(configName); if (log.isDebugEnabled()) { log.debug("Get configuration for context path {}, name '{}'", resource.getPath(), configName); } ConfigurationMetadata configMetadata = getConfigurationMetadata(configName); Resource configResource = null; Iterator<Resource> configResourceInheritanceChain = configurationResourceResolvingStrategy .getResourceInheritanceChain(resource, configurationResourceResolverConfig.configBucketNames(), configName); if (configResourceInheritanceChain != null) { ResettableIterator resettableConfigResourceInheritanceChain = new ListIteratorWrapper(configResourceInheritanceChain); configResource = applyPersistenceAndInheritance(resource.getPath(), configName, resettableConfigResourceInheritanceChain, false); if (configResource != null) { // get writeback resource for "reverse inheritance detection" Resource writebackConfigResource = null; String writebackConfigResourcePath = null; for (String configBucketName : configurationResourceResolverConfig.configBucketNames()) { writebackConfigResourcePath = configurationResourceResolvingStrategy.getResourcePath(resource, configBucketName, configName); if (writebackConfigResourcePath != null) { writebackConfigResource = resource.getResourceResolver().getResource(writebackConfigResourcePath); if (writebackConfigResource != null) { writebackConfigResource = configurationPersistenceStrategy.getResource(writebackConfigResource); break; } } } if (log.isTraceEnabled() && configResource != null) { log.trace("+ Found config resource for context path " + resource.getPath() + ": " + configResource.getPath() + " " + MapUtil.traceOutput(configResource.getValueMap()) + ", " + "writeback config resource: " + writebackConfigResourcePath); } resettableConfigResourceInheritanceChain.reset(); return new ConfigurationDataImpl(configMetadata, configResource, writebackConfigResource, applyPersistence(resettableConfigResourceInheritanceChain, false), resource, configName, this, configurationManagementSettings, configurationOverrideMultiplexer, configurationPersistenceStrategy, false, null); } } if (configMetadata != null) { // if no config resource found but config metadata exist return empty config data with default values return new ConfigurationDataImpl(configMetadata, resource, configName, this, configurationManagementSettings, configurationOverrideMultiplexer, configurationPersistenceStrategy, false); } return null; } @SuppressWarnings("unchecked") @Override public ConfigurationCollectionData getConfigurationCollection(Resource resource, String configName) { ConfigNameUtil.ensureValidConfigName(configName); if (log.isDebugEnabled()) { log.debug("Get configuration collection for context path {}, name '{}'", resource.getPath(), configName); } ConfigurationMetadata configMetadata = getConfigurationMetadata(configName); List<ConfigurationData> configData = new ArrayList<>(); // get all possible colection parent config names Collection<String> collectionParentConfigNames = configurationPersistenceStrategy.getAllCollectionParentConfigNames(configName); // get configuration resource items List<Iterator<Resource>> configResourceInheritanceChains = new ArrayList<>(); for (String collectionParentConfigName : collectionParentConfigNames) { Collection<Iterator<Resource>> result = configurationResourceResolvingStrategy .getResourceCollectionInheritanceChain(resource, configurationResourceResolverConfig.configBucketNames(), collectionParentConfigName); if (result != null) { configResourceInheritanceChains.addAll(result); } } String writebackConfigResourceCollectionParentPath = null; if (configResourceInheritanceChains != null) { for (Iterator<Resource> configResourceInheritanceChain : configResourceInheritanceChains) { ResettableIterator resettableConfigResourceInheritanceChain = new ListIteratorWrapper(configResourceInheritanceChain); Resource configResource = applyPersistenceAndInheritance(resource.getPath(), configName, resettableConfigResourceInheritanceChain, true); resettableConfigResourceInheritanceChain.reset(); Resource untransformedConfigResource = (Resource)resettableConfigResourceInheritanceChain.next(); if (configResource != null) { // get writeback resource for "reverse inheritance detection" Resource writebackConfigResource = null; String writebackConfigResourcePath = null; for (String configBucketName : configurationResourceResolverConfig.configBucketNames()) { writebackConfigResourceCollectionParentPath = configurationResourceResolvingStrategy.getResourceCollectionParentPath(resource, configBucketName, configName); if (writebackConfigResourceCollectionParentPath != null) { writebackConfigResourceCollectionParentPath = configurationPersistenceStrategy.getCollectionParentResourcePath(writebackConfigResourceCollectionParentPath); writebackConfigResourcePath = writebackConfigResourceCollectionParentPath + "/" + untransformedConfigResource.getName(); writebackConfigResource = configResource.getResourceResolver().getResource(writebackConfigResourcePath); if (writebackConfigResource != null) { writebackConfigResource = configurationPersistenceStrategy.getCollectionItemResource(writebackConfigResource); break; } } } if (log.isTraceEnabled() && configResource != null) { log.trace("+ Found config resource for context path " + resource.getPath() + ": " + configResource.getPath() + " " + MapUtil.traceOutput(configResource.getValueMap()) + ", " + "writeback config resource: " + writebackConfigResourcePath); } resettableConfigResourceInheritanceChain.reset(); configData.add(new ConfigurationDataImpl(configMetadata, configResource, writebackConfigResource, applyPersistence(resettableConfigResourceInheritanceChain, true), resource, configName, this, configurationManagementSettings, configurationOverrideMultiplexer, configurationPersistenceStrategy, true, untransformedConfigResource.getName())); } } } // fallback for writeback path detection when no configuration resources does exist yet if (writebackConfigResourceCollectionParentPath == null) { for (String configBucketName : configurationResourceResolverConfig.configBucketNames()) { writebackConfigResourceCollectionParentPath = configurationResourceResolvingStrategy.getResourceCollectionParentPath(resource, configBucketName, configName); if (writebackConfigResourceCollectionParentPath != null) { break; } } } // get properties of parent resource of the current level Map<String,Object> resourceCollectionParentProps = null; if (writebackConfigResourceCollectionParentPath != null) { Resource writebackConfigResourceCollectionParent = resource.getResourceResolver().getResource(writebackConfigResourceCollectionParentPath); if (writebackConfigResourceCollectionParent != null) { resourceCollectionParentProps = writebackConfigResourceCollectionParent.getValueMap(); } } return new ConfigurationCollectionDataImpl( configName, configData, writebackConfigResourceCollectionParentPath, resourceCollectionParentProps, configurationManagementSettings ); } @SuppressWarnings("unchecked") private Iterator<Resource> applyPersistence(final Iterator<Resource> configResourceInheritanceChain, final boolean isCollection) { if (configResourceInheritanceChain == null) { return null; } return IteratorUtils.transformedIterator(configResourceInheritanceChain, new Transformer() { @Override public Object transform(Object input) { if (isCollection) { return configurationPersistenceStrategy.getCollectionItemResource((Resource)input); } else { return configurationPersistenceStrategy.getResource((Resource)input); } } }); } private Resource applyPersistenceAndInheritance(String contextPath, String configName, Iterator<Resource> configResourceInheritanceChain, boolean isCollection) { if (configResourceInheritanceChain == null) { return null; } // apply configuration persistence transformation Iterator<Resource> transformedConfigResources = applyPersistence(configResourceInheritanceChain, isCollection); // apply resource inheritance Resource configResource = configurationInheritanceStrategy.getResource(transformedConfigResources); // apply overrides return configurationOverrideMultiplexer.overrideProperties(contextPath, configName, configResource); } @Override public void persistConfiguration(Resource resource, String configName, ConfigurationPersistData data) { ConfigNameUtil.ensureValidConfigName(configName); String configResourcePath = configurationResourceResolvingStrategy.getResourcePath(resource, CONFIGS_BUCKET_NAME, configName); if (configResourcePath == null) { throw new ConfigurationPersistenceException("Unable to persist configuration: Configuration resolving strategy returned no path."); } if (log.isDebugEnabled()) { log.debug("Persist configuration for context path {}, name '{}' to {}", resource.getPath(), configName, configResourcePath); } if (!configurationPersistenceStrategy.persistConfiguration(resource.getResourceResolver(), configResourcePath, data)) { throw new ConfigurationPersistenceException("Unable to persist configuration: No persistence strategy found."); } } @Override public void persistConfigurationCollection(Resource resource, String configName, ConfigurationCollectionPersistData data) { ConfigNameUtil.ensureValidConfigName(configName); String configResourceParentPath = configurationResourceResolvingStrategy.getResourceCollectionParentPath(resource, CONFIGS_BUCKET_NAME, configName); if (configResourceParentPath == null) { throw new ConfigurationPersistenceException("Unable to persist configuration collection: Configuration resolving strategy returned no parent path."); } if (log.isDebugEnabled()) { log.debug("Persist configuration collection for context path {}, name '{}' to {}", resource.getPath(), configName, configResourceParentPath); } if (!configurationPersistenceStrategy.persistConfigurationCollection(resource.getResourceResolver(), configResourceParentPath, data)) { throw new ConfigurationPersistenceException("Unable to persist configuration: No persistence strategy found."); } } @Override public ConfigurationData newCollectionItem(Resource resource, String configName) { ConfigNameUtil.ensureValidConfigName(configName); ConfigurationMetadata configMetadata = getConfigurationMetadata(configName); if (configMetadata != null) { return new ConfigurationDataImpl(configMetadata, resource, configName, this, configurationManagementSettings, configurationOverrideMultiplexer, configurationPersistenceStrategy, true); } return null; } @Override public void deleteConfiguration(Resource resource, String configName) { ConfigNameUtil.ensureValidConfigName(configName); // try to delete from all config bucket names boolean foundAnyPath = false; for (String configBucketName : configurationResourceResolverConfig.configBucketNames()) { String configResourcePath = configurationResourceResolvingStrategy.getResourcePath(resource, configBucketName, configName); if (configResourcePath != null) { foundAnyPath = true; if (log.isDebugEnabled()) { log.debug("Delete configuration for context path {}, name '{}' from {}", resource.getPath(), configName, configResourcePath); } if (!configurationPersistenceStrategy.deleteConfiguration(resource.getResourceResolver(), configResourcePath)) { throw new ConfigurationPersistenceException("Unable to delete configuration: No persistence strategy found."); } } } if (!foundAnyPath) { throw new ConfigurationPersistenceException("Unable to delete configuration: Configuration resolving strategy returned no path."); } } @Override public SortedSet<String> getConfigurationNames() { return configurationMetadataProvider.getConfigurationNames(); } @Override public ConfigurationMetadata getConfigurationMetadata(String configName) { ConfigNameUtil.ensureValidConfigName(configName); ConfigurationMetadata metadata = configurationMetadataProvider.getConfigurationMetadata(configName); if (metadata != null) { log.trace("+ Configuration metadata found for: {}", configName); return metadata; } // if no metadata found with direct match try to resolve nested configuration metadata references for (String partialConfigName : ConfigNameUtil.getAllPartialConfigNameVariations(configName)) { ConfigurationMetadata partialConfigMetadata = getConfigurationMetadata(partialConfigName); if (partialConfigMetadata != null) { ConfigurationMetadata nestedConfigMetadata = getNestedConfigurationMetadata(partialConfigMetadata, configName, partialConfigName); if (nestedConfigMetadata != null) { log.trace("+ Nested configuration metadata found for: {}", configName); return nestedConfigMetadata; } } } log.trace("- No configuration metadata found for: {}", configName); return null; } private ConfigurationMetadata getNestedConfigurationMetadata(ConfigurationMetadata configMetadata, String configName, String partialConfigName) { if (StringUtils.startsWith(configName, partialConfigName + "/")) { // depending on different persistence strategies config names can be transformed differently - try all combinations here Set<String> prefixesToRemove = new LinkedHashSet<>(); if (configMetadata.isCollection()) { String collectionItemName = StringUtils.substringBefore(StringUtils.substringAfter(configName, partialConfigName + "/"), "/"); for (String collectionParentConfigName : configurationPersistenceStrategy.getAllCollectionParentConfigNames(partialConfigName)) { for (String collectionItemConfigName : configurationPersistenceStrategy.getAllCollectionItemConfigNames(collectionItemName)) { prefixesToRemove.add(collectionParentConfigName + "/" + collectionItemConfigName + "/"); } } } else { for (String configNameItem : configurationPersistenceStrategy.getAllConfigNames(partialConfigName)) { prefixesToRemove.add(configNameItem + "/"); } } for (String prefixToRemove : prefixesToRemove) { String remainingConfigName = StringUtils.substringAfter(configName, prefixToRemove); // try direct match ConfigurationMetadata nestedConfigMetadata = getNestedConfigurationMetadataFromProperty(configMetadata, remainingConfigName); if (nestedConfigMetadata != null) { return nestedConfigMetadata; } // try to find partial match for deeper nestings for (String partialRemainingConfigName : ConfigNameUtil.getAllPartialConfigNameVariations(remainingConfigName)) { ConfigurationMetadata partialConfigMetadata = getNestedConfigurationMetadataFromProperty(configMetadata, partialRemainingConfigName); if (partialConfigMetadata != null) { nestedConfigMetadata = getNestedConfigurationMetadata(partialConfigMetadata, remainingConfigName, partialRemainingConfigName); if (nestedConfigMetadata != null) { return nestedConfigMetadata; } } } } } return null; } private ConfigurationMetadata getNestedConfigurationMetadataFromProperty(ConfigurationMetadata partialConfigMetadata, String configName) { for (PropertyMetadata<?> propertyMetadata : partialConfigMetadata.getPropertyMetadata().values()) { if (propertyMetadata.isNestedConfiguration()) { ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); if (StringUtils.equals(configName, nestedConfigMetadata.getName())) { return nestedConfigMetadata; } } } return null; } @Override public String getPersistenceResourcePath(String configResourcePath) { return configurationPersistenceStrategy.getResourcePath(configResourcePath); } }