This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.featureflags-1.0.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-featureflags.git
commit 8bf39037ca76fdc3a919734fa82c93d08efbca1f Author: Carsten Ziegeler <[email protected]> AuthorDate: Tue Dec 31 13:12:05 2013 +0000 Take III, add Feature interface and support adapting to functionality interfaces git-svn-id: https://svn.apache.org/repos/asf/sling/whiteboard/feature-flags@1554505 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/sling/featureflags/ClientContext.java | 2 +- .../{ClientContext.java => Feature.java} | 27 ++-- .../apache/sling/featureflags/FeatureProvider.java | 28 +---- .../org/apache/sling/featureflags/Features.java | 14 ++- .../{ClientContext.java => ResourceHiding.java} | 28 ++--- ...{ClientContext.java => ResourceTypeMapper.java} | 28 ++--- .../sling/featureflags/impl/ClientContextImpl.java | 57 +++++++-- .../sling/featureflags/impl/FeatureManager.java | 140 +++++++++++---------- .../sling/featureflags/impl/FeaturesImpl.java | 11 ++ .../featureflags/impl/ResourceAccessImpl.java | 5 +- .../featureflags/impl/ResourceDecoratorImpl.java | 5 +- 11 files changed, 195 insertions(+), 150 deletions(-) diff --git a/src/main/java/org/apache/sling/featureflags/ClientContext.java b/src/main/java/org/apache/sling/featureflags/ClientContext.java index 90fca71..61d5814 100644 --- a/src/main/java/org/apache/sling/featureflags/ClientContext.java +++ b/src/main/java/org/apache/sling/featureflags/ClientContext.java @@ -39,5 +39,5 @@ public interface ClientContext { * Returns a list of all enabled features * @return The list of features, the list might be empty. */ - Collection<String> getEnabledFeatures(); + Collection<Feature> getEnabledFeatures(); } diff --git a/src/main/java/org/apache/sling/featureflags/ClientContext.java b/src/main/java/org/apache/sling/featureflags/Feature.java similarity index 62% copy from src/main/java/org/apache/sling/featureflags/ClientContext.java copy to src/main/java/org/apache/sling/featureflags/Feature.java index 90fca71..9dc0f26 100644 --- a/src/main/java/org/apache/sling/featureflags/ClientContext.java +++ b/src/main/java/org/apache/sling/featureflags/Feature.java @@ -18,26 +18,29 @@ */ package org.apache.sling.featureflags; -import java.util.Collection; +import org.apache.sling.api.adapter.Adaptable; -import aQute.bnd.annotation.ProviderType; +import aQute.bnd.annotation.ConsumerType; /** - * The client context can be used by client code to check whether - * a specific feature is enable. - * A client context can be created through the {@link Features} service. + * A feature is defined by its name. + * Depending on the functionality the feature implements it can + * be adapted to different services, like + * <ul> + * <li>{@link ResourceHiding}</li> + * <li>{@link ResourceTypeMapper}</li> + * </ul> */ -@ProviderType -public interface ClientContext { +@ConsumerType +public interface Feature extends Adaptable { /** - * Returns <code>true</code> if the feature is enabled. + * The name of the feature. */ - boolean isEnabled(String featureName); + String getName(); /** - * Returns a list of all enabled features - * @return The list of features, the list might be empty. + * The description of the feature. */ - Collection<String> getEnabledFeatures(); + String getDescription(); } diff --git a/src/main/java/org/apache/sling/featureflags/FeatureProvider.java b/src/main/java/org/apache/sling/featureflags/FeatureProvider.java index 8e0c1ed..704246e 100644 --- a/src/main/java/org/apache/sling/featureflags/FeatureProvider.java +++ b/src/main/java/org/apache/sling/featureflags/FeatureProvider.java @@ -18,10 +18,6 @@ */ package org.apache.sling.featureflags; -import java.util.Map; - -import org.apache.sling.api.resource.Resource; - import aQute.bnd.annotation.ConsumerType; /** @@ -34,30 +30,10 @@ public interface FeatureProvider { * Checks whether the feature is enabled for the current execution * context. */ - boolean isEnabled(String featureName, ProviderContext context); + boolean isEnabled(Feature feature, ProviderContext context); /** * Return the list of available features from this provider. */ - String [] getFeatureNames(); - - /** - * Returns the resource type mapping for a feature. - * This mapping is only used if {@link #isEnabled(String, ExecutionContext)} - * return true for the given feature/context. The caller of this - * method must ensure to call {@link #isEnabled(String, ExecutionContext)} - * before calling this method and only call this method if - * {@link #isEnabled(String, ExecutionContext)} return <code>true</code> - */ - Map<String, String> getResourceTypeMapping(String featureName); - - /** - * Checks whether a resource should be hidden for a feature. - * This check is only executed if {@link #isEnabled(String, ExecutionContext)} - * return true for the given feature/context. The caller of this - * method must ensure to call {@link #isEnabled(String, ExecutionContext)} - * before calling this method and only call this method if - * {@link #isEnabled(String, ExecutionContext)} return <code>true</code> - */ - boolean hideResource(String featureName, Resource resource); + Feature[] getFeatures(); } diff --git a/src/main/java/org/apache/sling/featureflags/Features.java b/src/main/java/org/apache/sling/featureflags/Features.java index 25f69f3..1b054a9 100644 --- a/src/main/java/org/apache/sling/featureflags/Features.java +++ b/src/main/java/org/apache/sling/featureflags/Features.java @@ -32,12 +32,24 @@ import aQute.bnd.annotation.ProviderType; public interface Features { /** - * Get the list of all available features. A feature is available + * Get the list of all available feature names. A feature is available * if there is a {@link FeatureProvider} */ String[] getAvailableFeatureNames(); /** + * Get the list of all available features. A feature is available + * if there is a {@link FeatureProvider} + */ + Feature[] getAvailableFeatures(); + + /** + * Returns the feature with the given name. + * @return The feature or <code>null</code> + */ + Feature getFeature(String name); + + /** * Checks whether a feature with the given name is available. * A feature is available if there is a {@link FeatureProvider} * for that feature. diff --git a/src/main/java/org/apache/sling/featureflags/ClientContext.java b/src/main/java/org/apache/sling/featureflags/ResourceHiding.java similarity index 53% copy from src/main/java/org/apache/sling/featureflags/ClientContext.java copy to src/main/java/org/apache/sling/featureflags/ResourceHiding.java index 90fca71..7bd21ba 100644 --- a/src/main/java/org/apache/sling/featureflags/ClientContext.java +++ b/src/main/java/org/apache/sling/featureflags/ResourceHiding.java @@ -18,26 +18,24 @@ */ package org.apache.sling.featureflags; -import java.util.Collection; +import org.apache.sling.api.resource.Resource; -import aQute.bnd.annotation.ProviderType; +import aQute.bnd.annotation.ConsumerType; /** - * The client context can be used by client code to check whether - * a specific feature is enable. - * A client context can be created through the {@link Features} service. + * A {@link Feature} which is hiding resources can be adapted to + * this service interface. */ -@ProviderType -public interface ClientContext { +@ConsumerType +public interface ResourceHiding { /** - * Returns <code>true</code> if the feature is enabled. + * Checks whether a resource should be hidden for a feature. + * This check is only executed if {@link FeatureProvider#isEnabled(Feature, ExecutionContext)} + * return true for the given feature/context. The caller of this + * method must ensure to call {@link FeatureProvider#isEnabled(Feature, ExecutionContext)} + * before calling this method and only call this method if + * {@link FeatureProvider#isEnabled(Feature, ExecutionContext)} returned <code>true</code> */ - boolean isEnabled(String featureName); - - /** - * Returns a list of all enabled features - * @return The list of features, the list might be empty. - */ - Collection<String> getEnabledFeatures(); + boolean hideResource(Resource resource); } diff --git a/src/main/java/org/apache/sling/featureflags/ClientContext.java b/src/main/java/org/apache/sling/featureflags/ResourceTypeMapper.java similarity index 54% copy from src/main/java/org/apache/sling/featureflags/ClientContext.java copy to src/main/java/org/apache/sling/featureflags/ResourceTypeMapper.java index 90fca71..311990e 100644 --- a/src/main/java/org/apache/sling/featureflags/ClientContext.java +++ b/src/main/java/org/apache/sling/featureflags/ResourceTypeMapper.java @@ -18,26 +18,24 @@ */ package org.apache.sling.featureflags; -import java.util.Collection; +import java.util.Map; -import aQute.bnd.annotation.ProviderType; +import aQute.bnd.annotation.ConsumerType; /** - * The client context can be used by client code to check whether - * a specific feature is enable. - * A client context can be created through the {@link Features} service. + * A {@link Feature} which is mapping resource types can be adapted to + * this service interface. */ -@ProviderType -public interface ClientContext { +@ConsumerType +public interface ResourceTypeMapper { /** - * Returns <code>true</code> if the feature is enabled. + * Returns the resource type mapping for a feature. + * This mapping is only executed if {@link FeatureProvider#isEnabled(Feature, ExecutionContext)} + * return true for the given feature/context. The caller of this + * method must ensure to call {@link FeatureProvider#isEnabled(Feature, ExecutionContext)} + * before calling this method and only call this method if + * {@link FeatureProvider#isEnabled(Feature, ExecutionContext)} returned <code>true</code> */ - boolean isEnabled(String featureName); - - /** - * Returns a list of all enabled features - * @return The list of features, the list might be empty. - */ - Collection<String> getEnabledFeatures(); + Map<String, String> getResourceTypeMapping(); } diff --git a/src/main/java/org/apache/sling/featureflags/impl/ClientContextImpl.java b/src/main/java/org/apache/sling/featureflags/impl/ClientContextImpl.java index 651c947..e15b798 100644 --- a/src/main/java/org/apache/sling/featureflags/impl/ClientContextImpl.java +++ b/src/main/java/org/apache/sling/featureflags/impl/ClientContextImpl.java @@ -21,10 +21,14 @@ package org.apache.sling.featureflags.impl; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.List; import org.apache.sling.featureflags.ClientContext; +import org.apache.sling.featureflags.Feature; import org.apache.sling.featureflags.ProviderContext; +import org.apache.sling.featureflags.ResourceHiding; +import org.apache.sling.featureflags.ResourceTypeMapper; /** * Implementation of the client context @@ -33,15 +37,37 @@ public class ClientContextImpl implements ClientContext { private final ProviderContext featureContext; - private final List<String> enabledFeatures = new ArrayList<String>(); + private final List<Feature> enabledFeatures; - public ClientContextImpl(final ProviderContext featureContext) { - this.featureContext = featureContext; - } + private final List<ResourceHiding> hidingFeatures; + + private final List<ResourceTypeMapper> mapperFeatures; + + public ClientContextImpl(final ProviderContext featureContext, final List<Feature> features) { + Collections.sort(features, new Comparator<Feature>() { + + @Override + public int compare(final Feature arg0, final Feature arg1) { + return arg0.getName().compareTo(arg1.getName()); + } - public void addFeature(final String name) { - this.enabledFeatures.add(name); - Collections.sort(this.enabledFeatures); + }); + this.enabledFeatures = Collections.unmodifiableList(features); + final List<ResourceHiding> hiding = new ArrayList<ResourceHiding>(); + final List<ResourceTypeMapper> mapping = new ArrayList<ResourceTypeMapper>(); + for(final Feature f : this.enabledFeatures) { + final ResourceHiding rh = f.adaptTo(ResourceHiding.class); + if ( rh != null ) { + hiding.add(rh); + } + final ResourceTypeMapper rm = f.adaptTo(ResourceTypeMapper.class); + if ( rm != null ) { + mapping.add(rm); + } + } + this.hidingFeatures = hiding; + this.mapperFeatures = mapping; + this.featureContext = featureContext; } public ProviderContext getFeatureContext() { @@ -50,11 +76,24 @@ public class ClientContextImpl implements ClientContext { @Override public boolean isEnabled(final String featureName) { - return this.enabledFeatures.contains(featureName); + for(final Feature f : this.enabledFeatures) { + if ( featureName.equals(f.getName()) ) { + return true; + } + } + return false; } @Override - public Collection<String> getEnabledFeatures() { + public Collection<Feature> getEnabledFeatures() { return this.enabledFeatures; } + + public Collection<ResourceHiding> getHidingFeatures() { + return this.hidingFeatures; + } + + public Collection<ResourceTypeMapper> getMappingFeatures() { + return this.mapperFeatures; + } } diff --git a/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java b/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java index 092e62b..63faf52 100644 --- a/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java +++ b/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java @@ -29,13 +29,15 @@ import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.ReferencePolicy; import org.apache.sling.api.SlingHttpServletRequest; -import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.featureflags.ClientContext; +import org.apache.sling.featureflags.Feature; import org.apache.sling.featureflags.FeatureProvider; import org.apache.sling.featureflags.Features; import org.apache.sling.featureflags.ProviderContext; import org.osgi.framework.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This service implements the feature handling. @@ -48,33 +50,32 @@ import org.osgi.framework.Constants; referenceInterface=FeatureProvider.class) public class FeatureManager implements Features { + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + private final Map<String, List<FeatureProviderDescription>> providers = new HashMap<String, List<FeatureProviderDescription>>(); - private Map<String, FeatureProvider> activeProviders = new HashMap<String, FeatureProvider>(); + private Map<String, FeatureDescription> activeProviders = new HashMap<String, FeatureDescription>(); /** * Bind a new feature provider */ protected void bindFeatureProvider(final FeatureProvider provider, final Map<String, Object> props) { - final String[] features = provider.getFeatureNames(); + final Feature[] features = provider.getFeatures(); if ( features != null && features.length > 0 ) { - final FeatureProviderDescription info = new FeatureProviderDescription(provider, props); synchronized ( this.providers ) { boolean changed = false; - for(final String n : features) { - if ( n != null ) { - final String name = n.trim(); - if ( name.length() > 0 ) { - List<FeatureProviderDescription> candidates = this.providers.get(name); - if ( candidates == null ) { - candidates = new ArrayList<FeatureProviderDescription>(); - this.providers.put(name, candidates); - } - candidates.add(info); - Collections.sort(candidates); - changed = true; - } + for(final Feature f : features) { + final String name = f.getName(); + final FeatureProviderDescription info = new FeatureProviderDescription(provider, props, f); + + List<FeatureProviderDescription> candidates = this.providers.get(name); + if ( candidates == null ) { + candidates = new ArrayList<FeatureProviderDescription>(); + this.providers.put(name, candidates); } + candidates.add(info); + Collections.sort(candidates); + changed = true; } if ( changed ) { this.calculateActiveProviders(); @@ -87,23 +88,20 @@ public class FeatureManager implements Features { * Unbind a feature provider */ protected void unbindFeatureProvider(final FeatureProvider provider, final Map<String, Object> props) { - final String[] features = provider.getFeatureNames(); + final Feature[] features = provider.getFeatures(); if ( features != null && features.length > 0 ) { - final FeatureProviderDescription info = new FeatureProviderDescription(provider, props); synchronized ( this.providers ) { boolean changed = false; - for(final String n : features) { - if ( n != null ) { - final String name = n.trim(); - if ( name.length() > 0 ) { - final List<FeatureProviderDescription> candidates = this.providers.get(name); - if ( candidates != null ) { // sanity check - candidates.remove(info); - if ( candidates.size() == 0 ) { - this.providers.remove(name); - changed = true; - } - } + for(final Feature f : features) { + final String name = f.getName(); + final FeatureProviderDescription info = new FeatureProviderDescription(provider, props, f); + + final List<FeatureProviderDescription> candidates = this.providers.get(name); + if ( candidates != null ) { // sanity check + candidates.remove(info); + if ( candidates.size() == 0 ) { + this.providers.remove(name); + changed = true; } } } @@ -115,9 +113,16 @@ public class FeatureManager implements Features { } private void calculateActiveProviders() { - final Map<String, FeatureProvider> activeMap = new HashMap<String, FeatureProvider>(); + final Map<String, FeatureDescription> activeMap = new HashMap<String, FeatureDescription>(); for(final Map.Entry<String, List<FeatureProviderDescription>> entry : this.providers.entrySet()) { - activeMap.put(entry.getKey(), entry.getValue().get(0).getProvider()); + final FeatureProviderDescription desc = entry.getValue().get(0); + final FeatureDescription info = new FeatureDescription(); + info.feature = desc.feature; + info.provider = desc.provider; + activeMap.put(entry.getKey(), info); + if ( entry.getValue().size() > 1 ) { + logger.warn("More than one feature provider for feature {}", entry.getKey()); + } } this.activeProviders = activeMap; } @@ -160,21 +165,40 @@ public class FeatureManager implements Features { } private ClientContextImpl createClientContext(final ProviderContext providerContext) { - final ClientContextImpl ctx = new ClientContextImpl(providerContext); + final List<Feature> enabledFeatures = new ArrayList<Feature>(); - for(final Map.Entry<String, FeatureProvider> entry : this.activeProviders.entrySet()) { - final String name = entry.getKey(); - final FeatureProvider provider = entry.getValue(); + for(final Map.Entry<String, FeatureDescription> entry : this.activeProviders.entrySet()) { + final Feature f = entry.getValue().feature; - if ( provider.isEnabled(name, providerContext) ) { - ctx.addFeature(name); + if ( entry.getValue().provider.isEnabled(f, providerContext) ) { + enabledFeatures.add(f); } } + final ClientContextImpl ctx = new ClientContextImpl(providerContext, enabledFeatures); return ctx; } @Override + public Feature[] getAvailableFeatures() { + final List<Feature> result = new ArrayList<Feature>(); + for(final Map.Entry<String, FeatureDescription> entry : this.activeProviders.entrySet()) { + final Feature f = entry.getValue().feature; + result.add(f); + } + return result.toArray(new Feature[result.size()]); + } + + @Override + public Feature getFeature(final String name) { + final FeatureDescription desc = this.activeProviders.get(name); + if ( desc != null ) { + return desc.feature; + } + return null; + } + + @Override public String[] getAvailableFeatureNames() { return this.activeProviders.keySet().toArray(new String[this.activeProviders.size()]); } @@ -185,29 +209,20 @@ public class FeatureManager implements Features { } /** - * Checks whether a resource should be hidden for a feature. - * This check is only executed if {@link #isEnabled(String, ClientContext)} - * return true for the given feature/context. - */ - public boolean hideResource(final String featureName, final Resource resource) { - final FeatureProvider prod = this.activeProviders.get(featureName); - if ( prod != null ) { - return prod.hideResource(featureName, resource); - } - return false; - } - - /** * Internal class caching some provider infos like service id and ranking. */ private final static class FeatureProviderDescription implements Comparable<FeatureProviderDescription> { - public FeatureProvider provider; + public final FeatureProvider provider; public final int ranking; public final long serviceId; + public final Feature feature; - public FeatureProviderDescription(final FeatureProvider provider, final Map<String, Object> props) { + public FeatureProviderDescription(final FeatureProvider provider, + final Map<String, Object> props, + final Feature feature) { this.provider = provider; + this.feature = feature; final Object sr = props.get(Constants.SERVICE_RANKING); if ( sr == null || !(sr instanceof Integer)) { this.ranking = 0; @@ -243,20 +258,11 @@ public class FeatureManager implements Features { result = prime * result + (int) (serviceId ^ (serviceId >>> 32)); return result; } - - public FeatureProvider getProvider() { - return provider; - } } - public String getResourceType(final String featureName, final String resourceType) { - final FeatureProvider prod = this.activeProviders.get(featureName); - if ( prod != null ) { - final Map<String, String> mapping = prod.getResourceTypeMapping(featureName); - if ( mapping != null ) { - return mapping.get(resourceType); - } - } - return null; + private final static class FeatureDescription { + public Feature feature; + public FeatureProvider provider; + } } diff --git a/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java b/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java index 1c5ce53..e0e836e 100644 --- a/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java +++ b/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java @@ -24,6 +24,7 @@ import org.apache.felix.scr.annotations.Service; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.featureflags.ClientContext; +import org.apache.sling.featureflags.Feature; import org.apache.sling.featureflags.Features; /** @@ -47,6 +48,16 @@ public class FeaturesImpl implements Features { } @Override + public Feature[] getAvailableFeatures() { + return this.manager.getAvailableFeatures(); + } + + @Override + public Feature getFeature(final String name) { + return this.manager.getFeature(name); + } + + @Override public ClientContext getCurrentClientContext() { return this.manager.getCurrentClientContext(); } diff --git a/src/main/java/org/apache/sling/featureflags/impl/ResourceAccessImpl.java b/src/main/java/org/apache/sling/featureflags/impl/ResourceAccessImpl.java index 3164e7b..2dd05f1 100644 --- a/src/main/java/org/apache/sling/featureflags/impl/ResourceAccessImpl.java +++ b/src/main/java/org/apache/sling/featureflags/impl/ResourceAccessImpl.java @@ -23,6 +23,7 @@ import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.sling.api.resource.Resource; import org.apache.sling.featureflags.ClientContext; +import org.apache.sling.featureflags.ResourceHiding; import org.apache.sling.resourceaccesssecurity.AllowingResourceAccessGate; import org.apache.sling.resourceaccesssecurity.ResourceAccessGate; @@ -43,8 +44,8 @@ public class ResourceAccessImpl boolean available = true; final ClientContext info = manager.getCurrentClientContext(); if ( info != null ) { - for(final String name : info.getEnabledFeatures()) { - available = !manager.hideResource(name, resource); + for(final ResourceHiding f : ((ClientContextImpl)info).getHidingFeatures() ) { + available = !f.hideResource(resource); if ( !available) { break; } diff --git a/src/main/java/org/apache/sling/featureflags/impl/ResourceDecoratorImpl.java b/src/main/java/org/apache/sling/featureflags/impl/ResourceDecoratorImpl.java index 12e76ea..2118d43 100644 --- a/src/main/java/org/apache/sling/featureflags/impl/ResourceDecoratorImpl.java +++ b/src/main/java/org/apache/sling/featureflags/impl/ResourceDecoratorImpl.java @@ -27,6 +27,7 @@ import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceDecorator; import org.apache.sling.api.resource.ResourceWrapper; import org.apache.sling.featureflags.ClientContext; +import org.apache.sling.featureflags.ResourceTypeMapper; /** * Resource decorator implementing the resource type mapping @@ -42,10 +43,10 @@ public class ResourceDecoratorImpl implements ResourceDecorator { public Resource decorate(final Resource resource) { final ClientContext info = manager.getCurrentClientContext(); if ( info != null ) { - for(final String name : info.getEnabledFeatures()) { + for(final ResourceTypeMapper f : ((ClientContextImpl)info).getMappingFeatures() ) { final String resourceType = resource.getResourceType(); - final String overwriteType = manager.getResourceType(name, resourceType); + final String overwriteType = f.getResourceTypeMapping().get(resourceType); if ( overwriteType != null ) { return new ResourceWrapper(resource) { -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
