/*
* 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.impl.override;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.apache.sling.caconfig.impl.ConfigurationResourceWrapper;
import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer;
import org.apache.sling.caconfig.resource.impl.util.MapUtil;
import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider;
import org.apache.sling.commons.osgi.Order;
import org.apache.sling.commons.osgi.RankedServices;
import org.apache.sling.commons.osgi.RankedServices.ChangeListener;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Detects all {@link ConfigurationOverrideProvider} implementations in the container
* and consolidates their result based on service ranking.
*/
@Component(service = ConfigurationOverrideMultiplexer.class,
reference={
@Reference(name="configurationOverrideProvider", service=ConfigurationOverrideProvider.class,
bind="bindConfigurationOverrideProvider", unbind="unbindConfigurationOverrideProvider",
cardinality=ReferenceCardinality.MULTIPLE,
policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY)
})
public class ConfigurationOverrideMultiplexerImpl implements ConfigurationOverrideMultiplexer, ChangeListener {
private RankedServices<ConfigurationOverrideProvider> items = new RankedServices<>(Order.DESCENDING, this);
private volatile Collection<OverrideItem> allOverrides = Collections.emptyList();
private static final Logger log = LoggerFactory.getLogger(ConfigurationOverrideMultiplexerImpl.class);
protected void bindConfigurationOverrideProvider(ConfigurationOverrideProvider item, Map<String, Object> props) {
items.bind(item, props);
}
protected void unbindConfigurationOverrideProvider(ConfigurationOverrideProvider item, Map<String, Object> props) {
items.unbind(item, props);
}
/**
* Checks if the whole configuration for the given context path and name is overridden.
* @param contextPath Context path
* @param configName Config name
* @return true if the whole configuration is overridden.
*/
public boolean isAllOverridden(String contextPath, String configName) {
for (OverrideItem override : allOverrides) {
if (StringUtils.equals(configName, override.getConfigName()) && override.matchesPath(contextPath)) {
if (override.isAllProperties()) {
return true;
}
}
}
return false;
}
/**
* Override properties for given context path and configuration name.
* @param contextPath Path of context resource for which configuration was resolved
* @param configName Configuration name
* @param properties Resolved configuration properties
* @return Overwritten or replaced properties - or null if no override took place
*/
public Map<String,Object> overrideProperties(String contextPath, String configName, Map<String,Object> properties) {
if (allOverrides.size() == 0) {
return null;
}
boolean anyMatch = false;
Map<String,Object> overrideProperties = new HashMap<>(properties);
for (OverrideItem override : allOverrides) {
if (StringUtils.equals(configName, override.getConfigName()) && override.matchesPath(contextPath)) {
if (override.isAllProperties()) {
overrideProperties.clear();
}
overrideProperties.putAll(override.getProperties());
anyMatch = true;
}
}
if (anyMatch) {
return overrideProperties;
}
else {
return null;
}
}
/**
* Override properties in given configuration resource (if any overrides are defined).
* @param contextPath Context path
* @param configName Configuration name
* @param configResource Resolved configuration resource
* @return Resource with overwritten configuration properties - or original configuration resource if no override took place
*/
public Resource overrideProperties(String contextPath, String configName, Resource configResource) {
if (configResource == null) {
return null;
}
Map<String,Object> overrideProperties = overrideProperties(contextPath, configName, configResource.getValueMap());
if (overrideProperties == null) {
return configResource;
}
if (log.isTraceEnabled()) {
log.trace("! Override properties for context path " + contextPath + ", name '" + configName + "', "
+ "config path " + configResource.getPath() + ": "
+ MapUtil.traceOutput(configResource.getValueMap()) + " -> " + MapUtil.traceOutput(overrideProperties));
}
return new ConfigurationResourceWrapper(configResource, new ValueMapDecorator(overrideProperties));
}
/**
* If a provider is added or removed parse and collect all overrides again (to ensure correct overall order is preserved).
*/
@Override
public void changed() {
List<OverrideItem> overrides = new ArrayList<>();
for (ConfigurationOverrideProvider item : items) {
Collection<OverrideItem> itemOverrides = OverrideStringParser.parse(item.getOverrideStrings());
if (log.isDebugEnabled() && !itemOverrides.isEmpty()) {
log.debug("Override items from " + item.getClass().getName() + ":\n" + StringUtils.join(itemOverrides, "\n"));
}
overrides.addAll(itemOverrides);
}
allOverrides = overrides;
}
}