Added: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/ValueInfo.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/ValueInfo.java?rev=1761468&view=auto ============================================================================== --- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/ValueInfo.java (added) +++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/ValueInfo.java Mon Sep 19 15:39:17 2016 @@ -0,0 +1,80 @@ +/* + * 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.contextaware.config.management; + +import javax.annotation.CheckForNull; + +import org.apache.sling.contextaware.config.spi.metadata.PropertyMetadata; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Provides detailed information about a given configuration value. + * @param <T> Property type + */ +@ProviderType +public interface ValueInfo<T> { + + /** + * Property metadata. + * @return Property metadata. Null if no metadata exists. + */ + @CheckForNull PropertyMetadata<T> getPropertyMetadata(); + + /** + * Get value stored for the current context path. No inherited value. No default value. + * @return Value + */ + @CheckForNull T getValue(); + + /** + * Get value storedf or the current context path, or inherited from upper levels, or the default value. + * @return Value + */ + @CheckForNull T getEffectiveValue(); + + /** + * Get the path of the configuration resource the value is stored in. + * @return Resource path or null if no resource associated. + */ + @CheckForNull String getConfigSourcePath(); + + /** + * @return true if no value is defined but a default value is returned. + */ + boolean isDefault(); + + /** + * @return true if the value is not defined for the current context path but inherited from upper levels. + */ + // for future use + //boolean isInherited(); + + /** + * @return true if the value is overridden by an configuration override provider. + */ + // for future use + //boolean isOverridden(); + + /** + * @return true if this value is locked on a higher level and is not allowed to be overridden. + */ + // for future use + //boolean isLocked(); + +}
Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/ValueInfo.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/ValueInfo.java ------------------------------------------------------------------------------ --- svn:keywords (added) +++ svn:keywords Mon Sep 19 15:39:17 2016 @@ -0,0 +1 @@ +LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/ValueInfo.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationDataImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationDataImpl.java?rev=1761468&view=auto ============================================================================== --- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationDataImpl.java (added) +++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationDataImpl.java Mon Sep 19 15:39:17 2016 @@ -0,0 +1,97 @@ +/* + * 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.contextaware.config.management.impl; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.contextaware.config.management.ConfigurationData; +import org.apache.sling.contextaware.config.management.ValueInfo; +import org.apache.sling.contextaware.config.spi.metadata.ConfigurationMetadata; +import org.apache.sling.contextaware.config.spi.metadata.PropertyMetadata; + +final class ConfigurationDataImpl implements ConfigurationData { + + private final ValueMap properties; + private final String configSourcePath; + private final ConfigurationMetadata configMetadata; + + public ConfigurationDataImpl(Resource configResource, ConfigurationMetadata configMetadata) { + this(configResource.getValueMap(), configResource.getPath(), configMetadata); + } + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata) { + this(ValueMap.EMPTY, null, configMetadata); + } + + private ConfigurationDataImpl(ValueMap propertes, String configSourcePath, ConfigurationMetadata configMetadata) { + this.properties = propertes; + this.configSourcePath = configSourcePath; + this.configMetadata = configMetadata; + } + + @Override + public Set<String> getPropertyNames() { + Set<String> propertyNames = new HashSet<>(); + if (configMetadata != null) { + propertyNames.addAll(configMetadata.getPropertyMetadata().keySet()); + } + propertyNames.addAll(properties.keySet()); + return propertyNames; + } + + @Override + public ValueMap getValues() { + return properties; + } + + @Override + public ValueMap getEffectiveValues() { + Map<String,Object> props = new HashMap<>(); + if (configMetadata != null) { + for (PropertyMetadata<?> propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.getDefaultValue() != null) { + props.put(propertyMetadata.getName(), propertyMetadata.getDefaultValue()); + } + } + } + props.putAll(properties); + return new ValueMapDecorator(props); + } + + @SuppressWarnings("unchecked") + @Override + public ValueInfo<?> getValueInfo(String propertyName) { + PropertyMetadata propertyMetadata = configMetadata != null ? configMetadata.getPropertyMetadata().get(propertyName) : null; + Object value; + if (propertyMetadata != null) { + value = properties.get(propertyName, propertyMetadata.getType()); + } + else { + value = properties.get(propertyName); + } + return new ValueInfoImpl(value, configSourcePath, propertyMetadata); + } + +} Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationDataImpl.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationDataImpl.java ------------------------------------------------------------------------------ --- svn:keywords (added) +++ svn:keywords Mon Sep 19 15:39:17 2016 @@ -0,0 +1 @@ +LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationDataImpl.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationManagerImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationManagerImpl.java?rev=1761468&view=auto ============================================================================== --- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationManagerImpl.java (added) +++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationManagerImpl.java Mon Sep 19 15:39:17 2016 @@ -0,0 +1,107 @@ +/* + * 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.contextaware.config.management.impl; + +import static org.apache.sling.contextaware.config.impl.ConfigurationNameConstants.CONFIGS_PARENT_NAME; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.contextaware.config.impl.metadata.ConfigurationMetadataProviderMultiplexer; +import org.apache.sling.contextaware.config.management.ConfigurationData; +import org.apache.sling.contextaware.config.management.ConfigurationManager; +import org.apache.sling.contextaware.config.resource.ConfigurationResourceResolver; +import org.apache.sling.contextaware.config.resource.impl.ConfigurationResourceResolvingStrategyMultiplexer; +import org.apache.sling.contextaware.config.spi.ConfigurationPersistenceException; +import org.apache.sling.contextaware.config.spi.metadata.ConfigurationMetadata; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +@Component(service = ConfigurationManager.class) +public class ConfigurationManagerImpl implements ConfigurationManager { + + @Reference + private ConfigurationResourceResolver configurationResourceResolver; + @Reference + private ConfigurationResourceResolvingStrategyMultiplexer configurationResourceResolvingStrategy; + @Reference + private ConfigurationMetadataProviderMultiplexer configurationMetadataProvider; + @Reference + private ConfigurationPersistenceStrategyMultiplexer configurationPersistenceStrategy; + + @Override + public ConfigurationData get(Resource resource, String configName) { + ConfigurationMetadata configMetadata = configurationMetadataProvider.getConfigurationMetadata(configName); + Resource configResource = configurationResourceResolver.getResource(resource, CONFIGS_PARENT_NAME, configName); + if (configResource != null) { + return new ConfigurationDataImpl(configurationPersistenceStrategy.getResource(configResource), configMetadata); + } + if (configMetadata != null) { + // if no config resource found but config metadata exist return empty config data with default values + return new ConfigurationDataImpl(configMetadata); + } + return null; + } + + @Override + public Collection<ConfigurationData> getCollection(Resource resource, String configName) { + ConfigurationMetadata configMetadata = configurationMetadataProvider.getConfigurationMetadata(configName); + Collection<Resource> configResources = configurationResourceResolver.getResourceCollection(resource, CONFIGS_PARENT_NAME, configName); + List<ConfigurationData> configData = new ArrayList<>(); + for (Resource configResource : configResources) { + configData.add(new ConfigurationDataImpl(configurationPersistenceStrategy.getResource(configResource), configMetadata)); + } + return configData; + } + + @Override + public void persist(Resource resource, String configName, Map<String,Object> values) { + String configResourcePath = configurationResourceResolvingStrategy.getResourcePath(resource, CONFIGS_PARENT_NAME, configName); + if (configResourcePath == null) { + throw new ConfigurationPersistenceException("Unable to persist configuration: Configuration resolving strategy returned no path."); + } + if (!configurationPersistenceStrategy.persist(resource.getResourceResolver(), configResourcePath, values)) { + throw new ConfigurationPersistenceException("Unable to persist configuration: No persistence strategy found."); + } + } + + @Override + public void persistCollection(Resource resource, String configName, Collection<Map<String,Object>> values) { + String configResourceParentPath = configurationResourceResolvingStrategy.getResourceCollectionParentPath(resource, CONFIGS_PARENT_NAME, configName); + if (configResourceParentPath == null) { + throw new ConfigurationPersistenceException("Unable to persist configuration collection: Configuration resolving strategy returned no parent path."); + } + if (!configurationPersistenceStrategy.persistCollection(resource.getResourceResolver(), configResourceParentPath, values)) { + throw new ConfigurationPersistenceException("Unable to persist configuration: No persistence strategy found."); + } + } + + @Override + public ConfigurationData newCollectionItem(String configName) { + ConfigurationMetadata configMetadata = configurationMetadataProvider.getConfigurationMetadata(configName); + if (configMetadata != null) { + return new ConfigurationDataImpl(configMetadata); + } + return null; + } + +} Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationManagerImpl.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationManagerImpl.java ------------------------------------------------------------------------------ --- svn:keywords (added) +++ svn:keywords Mon Sep 19 15:39:17 2016 @@ -0,0 +1 @@ +LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationManagerImpl.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationPersistenceStrategyMultiplexer.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationPersistenceStrategyMultiplexer.java?rev=1761468&view=auto ============================================================================== --- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationPersistenceStrategyMultiplexer.java (added) +++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationPersistenceStrategyMultiplexer.java Mon Sep 19 15:39:17 2016 @@ -0,0 +1,99 @@ +/* + * 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.contextaware.config.management.impl; + +import java.util.Collection; +import java.util.Map; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.apache.sling.contextaware.config.spi.ConfigurationPersistenceStrategy; +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; + +/** + * Detects all {@link ConfigurationPersistenceStrategy} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ConfigurationPersistenceStrategyMultiplexer.class, +reference={ + @Reference(name="configurationResourcePersistenceStrategy", service=ConfigurationPersistenceStrategy.class, + bind="bindConfigurationResourcePersistenceStrategy", unbind="unbindConfigurationResourcePersistenceStrategy", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ConfigurationPersistenceStrategyMultiplexer implements ConfigurationPersistenceStrategy { + + private RankedServices<ConfigurationPersistenceStrategy> items = new RankedServices<>(Order.DESCENDING); + + protected void bindConfigurationResourcePersistenceStrategy(ConfigurationPersistenceStrategy configurationResourcePersistenceStrategy, Map<String, Object> props) { + items.bind(configurationResourcePersistenceStrategy, props); + } + + protected void unbindConfigurationResourcePersistenceStrategy(ConfigurationPersistenceStrategy configurationResourcePersistenceStrategy, Map<String, Object> props) { + items.unbind(configurationResourcePersistenceStrategy, props); + } + + /** + * Transform the configuration resource by the first implementation that has an answer. + */ + @Override + public Resource getResource(Resource resource) { + for (ConfigurationPersistenceStrategy item : items) { + Resource result = item.getResource(resource); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Persist configuration data with the first implementation that accepts it. + */ + @Override + public boolean persist(ResourceResolver resourceResolver, String configResourcePath, Map<String,Object> properties) { + for (ConfigurationPersistenceStrategy item : items) { + if (item.persist(resourceResolver, configResourcePath, properties)) { + return true; + } + } + return false; + } + + /** + * Persist configuration data with the first implementation that accepts it. + */ + @Override + public boolean persistCollection(ResourceResolver resourceResolver, String configResourceCollectionParentPath, + Collection<Map<String,Object>> propertiesCollection) { + for (ConfigurationPersistenceStrategy item : items) { + if (item.persistCollection(resourceResolver, configResourceCollectionParentPath, propertiesCollection)) { + return true; + } + } + return false; + } + +} Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationPersistenceStrategyMultiplexer.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationPersistenceStrategyMultiplexer.java ------------------------------------------------------------------------------ --- svn:keywords (added) +++ svn:keywords Mon Sep 19 15:39:17 2016 @@ -0,0 +1 @@ +LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ConfigurationPersistenceStrategyMultiplexer.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ValueInfoImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ValueInfoImpl.java?rev=1761468&view=auto ============================================================================== --- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ValueInfoImpl.java (added) +++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ValueInfoImpl.java Mon Sep 19 15:39:17 2016 @@ -0,0 +1,64 @@ +/* + * 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.contextaware.config.management.impl; + +import org.apache.commons.lang3.ObjectUtils; +import org.apache.sling.contextaware.config.management.ValueInfo; +import org.apache.sling.contextaware.config.spi.metadata.PropertyMetadata; + +final class ValueInfoImpl<T> implements ValueInfo<T> { + + private final T value; + private final T defaultValue; + private final String configSourcePath; + private final PropertyMetadata<T> propertyMetadata; + + public ValueInfoImpl(T value, String configSourcePath, PropertyMetadata<T> propertyMetadata) { + this.value = value; + this.defaultValue = propertyMetadata != null ? propertyMetadata.getDefaultValue() : null; + this.configSourcePath = configSourcePath; + this.propertyMetadata = propertyMetadata; + } + + @Override + public PropertyMetadata<T> getPropertyMetadata() { + return propertyMetadata; + } + + @Override + public T getValue() { + return value; + } + + @Override + public T getEffectiveValue() { + return ObjectUtils.defaultIfNull(value, defaultValue); + } + + @Override + public String getConfigSourcePath() { + return configSourcePath; + } + + @Override + public boolean isDefault() { + return value == null && defaultValue != null; + } + +} Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ValueInfoImpl.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ValueInfoImpl.java ------------------------------------------------------------------------------ --- svn:keywords (added) +++ svn:keywords Mon Sep 19 15:39:17 2016 @@ -0,0 +1 @@ +LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/impl/ValueInfoImpl.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/package-info.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/package-info.java?rev=1761468&view=auto ============================================================================== --- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/package-info.java (added) +++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/package-info.java Mon Sep 19 15:39:17 2016 @@ -0,0 +1,25 @@ +/* + * 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. + */ +/** + * Management API of the Context-Aware configuration implementation. + * This API is only indented for advanced use cases like writing a configuration editor, + * not for "normal" applications just reading configuration. + */ +@org.osgi.annotation.versioning.Version("1.0.0") +package org.apache.sling.contextaware.config.management; Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/package-info.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/package-info.java ------------------------------------------------------------------------------ --- svn:keywords (added) +++ svn:keywords Mon Sep 19 15:39:17 2016 @@ -0,0 +1 @@ +LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/management/package-info.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Modified: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ConfigurationResourceResolverImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ConfigurationResourceResolverImpl.java?rev=1761468&r1=1761467&r2=1761468&view=diff ============================================================================== --- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ConfigurationResourceResolverImpl.java (original) +++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ConfigurationResourceResolverImpl.java Mon Sep 19 15:39:17 2016 @@ -20,255 +20,37 @@ package org.apache.sling.contextaware.co import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; +import java.util.Iterator; import java.util.List; -import java.util.Set; import org.apache.sling.api.resource.Resource; -import org.apache.sling.api.resource.ResourceUtil; import org.apache.sling.contextaware.config.resource.ConfigurationResourceResolver; -import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Deactivate; -import org.osgi.service.metatype.annotations.AttributeDefinition; -import org.osgi.service.metatype.annotations.Designate; -import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.osgi.service.component.annotations.Reference; @Component(service=ConfigurationResourceResolver.class, immediate=true) -@Designate(ocd=ConfigurationResourceResolverImpl.Config.class) public class ConfigurationResourceResolverImpl implements ConfigurationResourceResolver { - @ObjectClassDefinition(name="Apache Sling Context Aware Configuration Resolver", - description="Standardized access to configurations in the resource tree.") - public static @interface Config { - - @AttributeDefinition(name="Allowed paths", - description = "Whitelist of paths where configurations can reside in.") - String[] allowedPaths() default {"/conf", "/apps/conf", "/libs/conf"}; - - @AttributeDefinition(name="Fallback paths", - description = "Global fallback configurations, ordered from most specific (checked first) to least specific.") - String[] fallbackPaths() default {"/conf/global", "/apps/conf", "/libs/conf"}; - } - - private static final String PROPERTY_CONFIG = "sling:config-ref"; - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - private volatile Config configuration; - - Config getConfiguration() { - return this.configuration; - } - - @Activate - private void activate(final Config config) { - this.configuration = config; - } - - @Deactivate - private void deactivate() { - this.configuration = null; - } - - List<String> getResolvePaths(final Resource contentResource) { - final List<String> refPaths = new ArrayList<>(); - - // add all config references found in resource hierarchy - final List<ConfigReference> refs = new ArrayList<>(); - findConfigRefs(refs, contentResource); - for (ConfigReference ref : refs) { - refPaths.add(ref.getConfigReference()); - } - - // finally add the global fallbacks - if ( this.configuration.fallbackPaths() != null ) { - for(final String path : this.configuration.fallbackPaths()) { - logger.debug("[{}] fallback config => {}", refs.size(), path); - refPaths.add(path); - } - } - - return refPaths; - } - - /** - * Check the name. - * A name must not be null and relative. - * @param name The name - * @return {@code true} if it is valid - */ - private boolean checkName(final String name) { - if (name == null || name.isEmpty() || name.startsWith("/") || name.contains("../") ) { - return false; - } - return true; - } - - /** - * Find next configuration refrence for given resource or one of it's parents. - * @param startResource Resource to start searching - * @return Config reference or null if none found - */ - private ConfigReference findNextConfigRef(final Resource startResource) { - // start at resource, go up - Resource resource = startResource; - while (resource != null) { - String ref = getReference(resource); - if (ref != null) { - // if absolute path found we are (probably) done - if (ref.startsWith("/")) { - // combine full path if relativeRef is present - ref = ResourceUtil.normalize(ref); - - if (ref != null && !isAllowedConfigPath(ref)) { - logger.warn("Ignoring reference to {} from {} - not in allowed paths.", ref, resource.getPath()); - ref = null; - } - - if (ref != null && isFallbackConfigPath(ref)) { - logger.warn("Ignoring reference to {} from {} - already a fallback path.", ref, resource.getPath()); - ref = null; - } - - if (ref != null) { - return new ConfigReference(resource, ref); - } - - } else { - logger.error("Invalid relative reference found for {} : {}. This entry is ignored", resource.getPath(), ref); - } - } - // if getParent() returns null, stop - resource = resource.getParent(); - } - - // if hit root and nothing found, return null - return null; - } - - /** - * Searches the resource hierarchy upwards for all config references and returns them. - * @param refs List to add found resources to - * @param startResource Resource to start searching - */ - private void findConfigRefs(final List<ConfigReference> refs, final Resource startResource) { - ConfigReference ref = findNextConfigRef(startResource); - if (ref != null) { - refs.add(ref); - findConfigRefs(refs, ref.getContentResource().getParent()); - } - } - - private String getReference(final Resource resource) { - final String ref = resource.getValueMap().get(PROPERTY_CONFIG, String.class); - logger.trace("Reference '{}' found at {}", ref, resource.getPath()); - - return ref; - } - - private boolean isAllowedConfigPath(String path) { - if (this.configuration.allowedPaths() == null) { - return false; - } - for (String pattern : this.configuration.allowedPaths()) { - if (logger.isTraceEnabled()) { - logger.trace("- checking if '{}' starts with {}", path, pattern); - } - if (path.equals(pattern) || path.startsWith(pattern + "/")) { - return true; - } - } - return false; - } - - private boolean isFallbackConfigPath(final String ref) { - if ( this.configuration.fallbackPaths() != null ) { - for(final String name : this.configuration.fallbackPaths()) { - if ( name.equals(ref) ) { - return true; - } - } - } - return false; - } + @Reference + private ContextPathStrategyMultiplexer contextPathStrategy; + @Reference + private ConfigurationResourceResolvingStrategyMultiplexer configurationResourceResolvingStrategy; @Override - public Resource getResource(final Resource contentResource, final String bucketName, final String configName) { - if (contentResource == null || !checkName(bucketName) || !checkName(configName)) { - return null; - } - String name = bucketName + "/" + configName; - logger.debug("Searching {} for resource {}", name, contentResource.getPath()); - - // strategy: find first item among all configured paths - int idx = 1; - for (final String path : getResolvePaths(contentResource)) { - final Resource item = contentResource.getResourceResolver().getResource(ResourceUtil.normalize(path + "/" + name)); - if (item != null) { - logger.debug("Resolved config item at [{}]: {}", idx, item.getPath()); - - return item; - } - idx++; - } - - logger.debug("Could not resolve any config item for '{}' (or no permissions to read it)", name); - - // nothing found - return null; + public Resource getResource(Resource resource, String bucketName, String configName) { + return configurationResourceResolvingStrategy.getResource(resource, bucketName, configName); } @Override - public Collection<Resource> getResourceCollection(final Resource contentResource, final String bucketName, final String configName) { - if (contentResource == null || !checkName(bucketName) || !checkName(configName)) { - return Collections.emptyList(); - } - String name = bucketName + "/" + configName; - if (logger.isTraceEnabled()) { - logger.trace("- searching for list '{}'", name); - } - - final Set<String> names = new HashSet<>(); - final List<Resource> result = new ArrayList<>(); - int idx = 1; - for (String path : this.getResolvePaths(contentResource)) { - Resource item = contentResource.getResourceResolver().getResource(ResourceUtil.normalize(path + "/" + name)); - if (item != null) { - if (logger.isTraceEnabled()) { - logger.trace("+ resolved config item at [{}]: {}", idx, item.getPath()); - } - - for (Resource child : item.getChildren()) { - if ( !child.getName().contains(":") && !names.contains(child.getName()) ) { - result.add(child); - names.add(child.getName()); - } - } - - } else { - if (logger.isTraceEnabled()) { - logger.trace("- no item '{}' under config '{}'", name, path); - } - } - idx++; - } - - if (logger.isTraceEnabled()) { - logger.trace("- final list has {} items", result.size()); - } - - return result; + public Collection<Resource> getResourceCollection(Resource resource, String bucketName, String configName) { + return configurationResourceResolvingStrategy.getResourceCollection(resource, bucketName, configName); } @Override public String getContextPath(Resource resource) { - ConfigReference ref = findNextConfigRef(resource); - if (ref != null) { - return ref.getContentResource().getPath(); + Iterator<Resource> it = contextPathStrategy.findContextResources(resource); + if (it.hasNext()) { + return it.next().getPath(); } else { return null; @@ -278,28 +60,11 @@ public class ConfigurationResourceResolv @Override public Collection<String> getAllContextPaths(Resource resource) { final List<String> contextPaths = new ArrayList<>(); - final List<ConfigReference> refs = new ArrayList<>(); - findConfigRefs(refs, resource); - for (ConfigReference ref : refs) { - contextPaths.add(ref.getContentResource().getPath()); + Iterator<Resource> contextResources = contextPathStrategy.findContextResources(resource); + while (contextResources.hasNext()) { + contextPaths.add(contextResources.next().getPath()); } return contextPaths; } - private static class ConfigReference { - private final Resource contentResource; - private final String configReference; - - public ConfigReference(Resource contentResource, String configReference) { - this.contentResource = contentResource; - this.configReference = configReference; - } - public Resource getContentResource() { - return contentResource; - } - public String getConfigReference() { - return configReference; - } - } - } Added: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ConfigurationResourceResolvingStrategyMultiplexer.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ConfigurationResourceResolvingStrategyMultiplexer.java?rev=1761468&view=auto ============================================================================== --- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ConfigurationResourceResolvingStrategyMultiplexer.java (added) +++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ConfigurationResourceResolvingStrategyMultiplexer.java Mon Sep 19 15:39:17 2016 @@ -0,0 +1,113 @@ +/* + * 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.contextaware.config.resource.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.apache.sling.contextaware.config.resource.spi.ConfigurationResourceResolvingStrategy; +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; + +/** + * Detects all {@link ConfigurationResourceResolvingStrategy} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ConfigurationResourceResolvingStrategyMultiplexer.class, +reference={ + @Reference(name="configurationResourceResolvingStrategy", service=ConfigurationResourceResolvingStrategy.class, + bind="bindConfigurationResourceResolvingStrategy", unbind="unbindConfigurationResourceResolvingStrategy", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ConfigurationResourceResolvingStrategyMultiplexer implements ConfigurationResourceResolvingStrategy { + + private RankedServices<ConfigurationResourceResolvingStrategy> items = new RankedServices<>(Order.DESCENDING); + + protected void bindConfigurationResourceResolvingStrategy(ConfigurationResourceResolvingStrategy contextPathStrategy, Map<String, Object> props) { + items.bind(contextPathStrategy, props); + } + + protected void unbindConfigurationResourceResolvingStrategy(ConfigurationResourceResolvingStrategy contextPathStrategy, Map<String, Object> props) { + items.unbind(contextPathStrategy, props); + } + + /** + * Gets the configuration resource from the first implementation that has an answer. + */ + @Override + public Resource getResource(Resource resource, String bucketName, String configName) { + for (ConfigurationResourceResolvingStrategy item : items) { + Resource result = item.getResource(resource, bucketName, configName); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Merge the configuration resources from all implementations into a combined list. + */ + @Override + public Collection<Resource> getResourceCollection(Resource resource, String bucketName, String configName) { + List<Resource> result = new ArrayList<>(); + for (ConfigurationResourceResolvingStrategy item : items) { + result.addAll(item.getResourceCollection(resource, bucketName, configName)); + } + return result; + } + + /** + * Gets the configuration resource path from the first implementation that has an answer. + */ + @Override + public String getResourcePath(Resource resource, String bucketName, String configName) { + for (ConfigurationResourceResolvingStrategy item : items) { + String result = item.getResourcePath(resource, bucketName, configName); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Gets the configuration resource collection parent path from the first implementation that has an answer. + */ + @Override + public String getResourceCollectionParentPath(Resource resource, String bucketName, String configName) { + for (ConfigurationResourceResolvingStrategy item : items) { + String result = item.getResourceCollectionParentPath(resource, bucketName, configName); + if (result != null) { + return result; + } + } + return null; + } + +} Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ConfigurationResourceResolvingStrategyMultiplexer.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ConfigurationResourceResolvingStrategyMultiplexer.java ------------------------------------------------------------------------------ --- svn:keywords (added) +++ svn:keywords Mon Sep 19 15:39:17 2016 @@ -0,0 +1 @@ +LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ConfigurationResourceResolvingStrategyMultiplexer.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ContextPathStrategyMultiplexer.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ContextPathStrategyMultiplexer.java?rev=1761468&view=auto ============================================================================== --- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ContextPathStrategyMultiplexer.java (added) +++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ContextPathStrategyMultiplexer.java Mon Sep 19 15:39:17 2016 @@ -0,0 +1,127 @@ +/* + * 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.contextaware.config.resource.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections.Predicate; +import org.apache.commons.collections.iterators.CollatingIterator; +import org.apache.commons.collections.iterators.FilterIterator; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.apache.sling.contextaware.config.resource.spi.ContextPathStrategy; +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; + +/** + * Detects all {@link ContextPathStrategy} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ContextPathStrategyMultiplexer.class, +reference={ + @Reference(name="contextPathStrategy", service=ContextPathStrategy.class, + bind="bindContextPathStrategy", unbind="unbindContextPathStrategy", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ContextPathStrategyMultiplexer implements ContextPathStrategy { + + private static Comparator<Resource> PATH_LENGTH_COMPARATOR = new Comparator<Resource>() { + @Override + public int compare(Resource o1, Resource o2) { + Integer length1 = o1.getPath().length(); + Integer length2 = o2.getPath().length(); + return length2.compareTo(length1); + } + }; + + private RankedServices<ContextPathStrategy> items = new RankedServices<>(Order.DESCENDING); + + protected void bindContextPathStrategy(ContextPathStrategy contextPathStrategy, Map<String, Object> props) { + items.bind(contextPathStrategy, props); + } + + protected void unbindContextPathStrategy(ContextPathStrategy contextPathStrategy, Map<String, Object> props) { + items.unbind(contextPathStrategy, props); + } + + /** + * Merges all results from the detected implementations into a single answer. + */ + @Override + public Iterator<Resource> findContextResources(Resource resource) { + List<Iterator<Resource>> allResults = getAllResults(resource); + if (allResults.isEmpty()) { + return Collections.emptyIterator(); + } + if (allResults.size() == 1) { + return allResults.get(0); + } + return mergeResults(allResults); + } + + /** + * Get all results from all registered context path strategies. + * @param resource Start resource + * @return List of all results + */ + private List<Iterator<Resource>> getAllResults(Resource resource) { + List<Iterator<Resource>> results = new ArrayList<>(); + for (ContextPathStrategy item : items) { + Iterator<Resource> result = item.findContextResources(resource); + if (result.hasNext()) { + results.add(result); + } + } + return results; + } + + /** + * Merges results from different context path strategy implementations. + * Eliminating of duplicates and sorting is done solely based on path length. + * The contract of the ContextPathStrategy defines that only parents or the resource itself + * is returned, so the assumption should be safe. + * @param allResults List of all results + * @return Merged result + */ + @SuppressWarnings("unchecked") + private Iterator<Resource> mergeResults(List<Iterator<Resource>> allResults) { + return new FilterIterator(new CollatingIterator(PATH_LENGTH_COMPARATOR, allResults), + // eliminate duplicate resources reported by different implementations + new Predicate() { + private Set<String> resourcePaths = new HashSet<>(); + @Override + public boolean evaluate(Object object) { + return resourcePaths.add(((Resource)object).getPath()); + } + }); + } + +} Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ContextPathStrategyMultiplexer.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ContextPathStrategyMultiplexer.java ------------------------------------------------------------------------------ --- svn:keywords (added) +++ svn:keywords Mon Sep 19 15:39:17 2016 @@ -0,0 +1 @@ +LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/ContextPathStrategyMultiplexer.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultConfigurationResourceResolvingStrategy.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultConfigurationResourceResolvingStrategy.java?rev=1761468&view=auto ============================================================================== --- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultConfigurationResourceResolvingStrategy.java (added) +++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultConfigurationResourceResolvingStrategy.java Mon Sep 19 15:39:17 2016 @@ -0,0 +1,301 @@ +/* + * 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.contextaware.config.resource.impl.def; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.apache.commons.collections.PredicateUtils; +import org.apache.commons.collections.Transformer; +import org.apache.commons.collections.iterators.ArrayIterator; +import org.apache.commons.collections.iterators.FilterIterator; +import org.apache.commons.collections.iterators.IteratorChain; +import org.apache.commons.collections.iterators.TransformIterator; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.contextaware.config.resource.impl.ContextPathStrategyMultiplexer; +import org.apache.sling.contextaware.config.resource.spi.ConfigurationResourceResolvingStrategy; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(service=ConfigurationResourceResolvingStrategy.class) +@Designate(ocd=DefaultConfigurationResourceResolvingStrategy.Config.class) +public class DefaultConfigurationResourceResolvingStrategy implements ConfigurationResourceResolvingStrategy { + + @ObjectClassDefinition(name="Apache Sling Context-Aware Default Configuration Resource Resolving Strategy", + description="Standardized access to configurations in the resource tree.") + static @interface Config { + + @AttributeDefinition(name="Enabled", + description = "Enable this configuration resourcer resolving strategy.") + boolean enabled() default true; + + @AttributeDefinition(name="Allowed paths", + description = "Whitelist of paths where configurations can reside in.") + String[] allowedPaths() default {"/conf", "/apps/conf", "/libs/conf"}; + + @AttributeDefinition(name="Fallback paths", + description = "Global fallback configurations, ordered from most specific (checked first) to least specific.") + String[] fallbackPaths() default {"/conf/global", "/apps/conf", "/libs/conf"}; + } + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private volatile Config config; + + @Reference + private ContextPathStrategyMultiplexer contextPathStrategy; + + Config getConfiguration() { + return this.config; + } + + @Activate + private void activate(final Config config) { + this.config = config; + } + + @Deactivate + private void deactivate() { + this.config = null; + } + + @SuppressWarnings("unchecked") + Iterator<String> getResolvePaths(final Resource contentResource) { + return new IteratorChain( + // add all config references found in resource hierarchy + findConfigRefs(contentResource), + // finally add the global fallbacks + new ArrayIterator(this.config.fallbackPaths()) + ); + } + + /** + * Check the name. + * A name must not be null and relative. + * @param name The name + * @return {@code true} if it is valid + */ + private boolean checkName(final String name) { + if (name == null || name.isEmpty() || name.startsWith("/") || name.contains("../") ) { + return false; + } + return true; + } + + /** + * Searches the resource hierarchy upwards for all config references and returns them. + * @param refs List to add found resources to + * @param startResource Resource to start searching + */ + @SuppressWarnings("unchecked") + private Iterator<String> findConfigRefs(final Resource startResource) { + Iterator<Resource> contextResources = contextPathStrategy.findContextResources(startResource); + // get config resource path for each context resource, filter out items where not reference could be resolved + return new FilterIterator(new TransformIterator(contextResources, new Transformer() { + @Override + public Object transform(Object input) { + return getReference((Resource)input); + } + }), PredicateUtils.notNullPredicate()); + } + + private String getReference(final Resource resource) { + String ref = resource.getValueMap().get(DefaultContextPathStrategy.PROPERTY_CONFIG, String.class); + + if (ref != null) { + // if absolute path found we are (probably) done + if (ref.startsWith("/")) { + // combine full path if relativeRef is present + ref = ResourceUtil.normalize(ref); + + if (ref != null && !isAllowedConfigPath(ref)) { + logger.warn("Ignoring reference to {} from {} - not in allowed paths.", ref, resource.getPath()); + ref = null; + } + + if (ref != null && isFallbackConfigPath(ref)) { + logger.warn("Ignoring reference to {} from {} - already a fallback path.", ref, resource.getPath()); + ref = null; + } + + } else { + logger.error("Invalid relative reference found for {} : {}. This entry is ignored", resource.getPath(), ref); + } + } + + if (ref != null) { + logger.trace("Reference '{}' found at {}", ref, resource.getPath()); + } + + return ref; + } + + private boolean isAllowedConfigPath(String path) { + for (String pattern : this.config.allowedPaths()) { + if (logger.isTraceEnabled()) { + logger.trace("- checking if '{}' starts with {}", path, pattern); + } + if (path.equals(pattern) || path.startsWith(pattern + "/")) { + return true; + } + } + return false; + } + + private boolean isFallbackConfigPath(final String ref) { + for(final String name : this.config.fallbackPaths()) { + if ( name.equals(ref) ) { + return true; + } + } + return false; + } + + private boolean isEnabledAndParamsValid(final Resource contentResource, final String bucketName, final String configName) { + return config.enabled() && contentResource != null && checkName(bucketName) && checkName(configName); + } + + private String buildResourcePath(String path, String name) { + return ResourceUtil.normalize(path + "/" + name); + } + + @Override + public Resource getResource(final Resource contentResource, final String bucketName, final String configName) { + if (!isEnabledAndParamsValid(contentResource, bucketName, configName)) { + return null; + } + String name = bucketName + "/" + configName; + logger.debug("Searching {} for resource {}", name, contentResource.getPath()); + + // strategy: find first item among all configured paths + int idx = 1; + Iterator<String> paths = getResolvePaths(contentResource); + while (paths.hasNext()) { + final String path = paths.next(); + final Resource item = contentResource.getResourceResolver().getResource(buildResourcePath(path, name)); + if (item != null) { + logger.debug("Resolved config item at [{}]: {}", idx, item.getPath()); + + return item; + } + idx++; + } + + logger.debug("Could not resolve any config item for '{}' (or no permissions to read it)", name); + + // nothing found + return null; + } + + @Override + public Collection<Resource> getResourceCollection(final Resource contentResource, final String bucketName, final String configName) { + if (!isEnabledAndParamsValid(contentResource, bucketName, configName)) { + return Collections.emptyList(); + } + String name = bucketName + "/" + configName; + if (logger.isTraceEnabled()) { + logger.trace("- searching for list '{}'", name); + } + + final Set<String> names = new HashSet<>(); + final List<Resource> result = new ArrayList<>(); + int idx = 1; + Iterator<String> paths = getResolvePaths(contentResource); + while (paths.hasNext()) { + final String path = paths.next(); + Resource item = contentResource.getResourceResolver().getResource(buildResourcePath(path, name)); + if (item != null) { + if (logger.isTraceEnabled()) { + logger.trace("+ resolved config item at [{}]: {}", idx, item.getPath()); + } + + for (Resource child : item.getChildren()) { + if ( !child.getName().contains(":") && !names.contains(child.getName()) ) { + result.add(child); + names.add(child.getName()); + } + } + + } else { + if (logger.isTraceEnabled()) { + logger.trace("- no item '{}' under config '{}'", name, path); + } + } + idx++; + } + + if (logger.isTraceEnabled()) { + logger.trace("- final list has {} items", result.size()); + } + + return result; + } + + @Override + public String getResourcePath(Resource contentResource, String bucketName, String configName) { + if (!isEnabledAndParamsValid(contentResource, bucketName, configName)) { + return null; + } + String name = bucketName + "/" + configName; + + Iterator<String> configPaths = this.findConfigRefs(contentResource); + if (configPaths.hasNext()) { + String configPath = buildResourcePath(configPaths.next(), name); + logger.debug("Building configuration path {} for resource {}: {}", name, contentResource.getPath(), configPath); + return configPath; + } + else { + logger.debug("No configuration path {} foundfor resource {}.", name, contentResource.getPath()); + return null; + } + } + + @Override + public String getResourceCollectionParentPath(Resource contentResource, String bucketName, String configName) { + if (!isEnabledAndParamsValid(contentResource, bucketName, configName)) { + return null; + } + String name = bucketName + "/" + configName; + + Iterator<String> configPaths = this.findConfigRefs(contentResource); + if (configPaths.hasNext()) { + String configPath = buildResourcePath(configPaths.next(), name); + logger.debug("Building configuration collection parent path {} for resource {}: {}", name, contentResource.getPath(), configPath); + return configPath; + } + else { + logger.debug("No configuration collection parent path {} foundfor resource {}.", name, contentResource.getPath()); + return null; + } + } + +} Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultConfigurationResourceResolvingStrategy.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultConfigurationResourceResolvingStrategy.java ------------------------------------------------------------------------------ --- svn:keywords (added) +++ svn:keywords Mon Sep 19 15:39:17 2016 @@ -0,0 +1 @@ +LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultConfigurationResourceResolvingStrategy.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultContextPathStrategy.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultContextPathStrategy.java?rev=1761468&view=auto ============================================================================== --- sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultContextPathStrategy.java (added) +++ sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultContextPathStrategy.java Mon Sep 19 15:39:17 2016 @@ -0,0 +1,132 @@ +/* + * 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.contextaware.config.resource.impl.def; + +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.contextaware.config.resource.spi.ContextPathStrategy; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(service = ContextPathStrategy.class) +@Designate(ocd=DefaultContextPathStrategy.Config.class) +public class DefaultContextPathStrategy implements ContextPathStrategy { + + @ObjectClassDefinition(name="Apache Sling Context-Aware Default Context Path Strategy", + description="Detects context path by existence of " + PROPERTY_CONFIG + " properties.") + static @interface Config { + + @AttributeDefinition(name="Enabled", + description = "Enable this context path strategy.") + boolean enabled() default true; + + } + + /** + * Property that points to the configuration to be used. + * Additionally each resource having this property marks the beginning of a new context sub-tree. + */ + public static final String PROPERTY_CONFIG = "sling:config-ref"; + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private volatile Config config; + + @Activate + private void activate(ComponentContext componentContext, Config config) { + this.config = config; + } + + @Override + public Iterator<Resource> findContextResources(Resource resource) { + if (!config.enabled()) { + return Collections.emptyIterator(); + } + return new ConfigResourceIterator(resource); + } + + /** + * Searches the resource hierarchy upwards for all context and returns the root resource for each of them. + */ + private class ConfigResourceIterator implements Iterator<Resource> { + + private Resource next; + + public ConfigResourceIterator(Resource startResource) { + next = findNextContextResource(startResource); + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public Resource next() { + if (next == null) { + throw new NoSuchElementException(); + } + Resource result = next; + next = findNextContextResource(next.getParent()); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Find next configuration context root for given resource. + * @param startResource Resource to start searching + * @return Next resource with sling:config-ref property or null if none found. + */ + private Resource findNextContextResource(Resource startResource) { + // start at resource, go up + Resource resource = startResource; + + while (resource != null) { + if (hasConfigRef(resource)) { + log.trace("Found context path '{}'.", resource.getPath()); + return resource; + } + // if getParent() returns null, stop + resource = resource.getParent(); + } + + // if hit root and nothing found, return null + return null; + } + + private boolean hasConfigRef(final Resource resource) { + return resource.getValueMap().get(PROPERTY_CONFIG, String.class) != null; + } + + } + +} Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultContextPathStrategy.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultContextPathStrategy.java ------------------------------------------------------------------------------ --- svn:keywords (added) +++ svn:keywords Mon Sep 19 15:39:17 2016 @@ -0,0 +1 @@ +LastChangedDate LastChangedRevision LastChangedBy HeadURL Id Author Propchange: sling/trunk/contrib/extensions/contextaware-config/impl/src/main/java/org/apache/sling/contextaware/config/resource/impl/def/DefaultContextPathStrategy.java ------------------------------------------------------------------------------ svn:mime-type = text/plain