You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:34:33 UTC

[sling-org-apache-sling-featureflags] 14/25: SLING-3148 Merge back from whiteboard/fmeschbe/featureflags/feature-flags

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 5e66975857d4ad36946668ae7da2cb9e125acdbb
Author: Felix Meschberger <fm...@apache.org>
AuthorDate: Tue Jan 28 08:32:39 2014 +0000

    SLING-3148 Merge back from whiteboard/fmeschbe/featureflags/feature-flags
    
    - Removed ResourceHiding and ResourceTypeHiding
    - Added ResourceDecorator support
    - Added WebConsole plugin
    - Added Feature factory configuration support
    - Added JavaDoc
    - Support non-Sling ExecutionContext
    - Porperly register ClientContext filter as Sling filter
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/feature-flags@1561994 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  10 +-
 .../apache/sling/featureflags/ClientContext.java   |  22 +-
 .../sling/featureflags/ExecutionContext.java       |  35 ++-
 .../org/apache/sling/featureflags/Feature.java     |  38 ++--
 .../org/apache/sling/featureflags/Features.java    |  81 +++++--
 .../apache/sling/featureflags/ResourceHiding.java  |  41 ----
 .../sling/featureflags/ResourceTypeMapping.java    |  41 ----
 .../sling/featureflags/impl/ClientContextImpl.java |  49 ++---
 .../sling/featureflags/impl/ConfiguredFeature.java | 105 +++++++++
 .../impl/CurrentClientContextFilter.java           |  43 ++--
 .../featureflags/impl/ExecutionContextImpl.java    |  12 +-
 .../sling/featureflags/impl/FeatureManager.java    | 242 ++++++++++++---------
 ...atorImpl.java => FeatureResourceDecorator.java} |  40 ++--
 .../featureflags/impl/FeatureWebConsolePlugin.java |  74 +++++++
 .../sling/featureflags/impl/FeaturesImpl.java      |  17 +-
 .../featureflags/impl/ResourceAccessImpl.java      |  58 -----
 .../apache/sling/featureflags/package-info.java    |  42 +++-
 17 files changed, 563 insertions(+), 387 deletions(-)

diff --git a/pom.xml b/pom.xml
index 7f62fee..75a28cd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,19 +43,13 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.5.0</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.resourceaccesssecurity</artifactId>
-            <version>0.0.1-SNAPSHOT</version>
+            <version>2.1.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.osgi</artifactId>
-            <version>2.2.0</version>
+            <version>2.1.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/src/main/java/org/apache/sling/featureflags/ClientContext.java b/src/main/java/org/apache/sling/featureflags/ClientContext.java
index 61d5814..137e6ff 100644
--- a/src/main/java/org/apache/sling/featureflags/ClientContext.java
+++ b/src/main/java/org/apache/sling/featureflags/ClientContext.java
@@ -23,21 +23,31 @@ import java.util.Collection;
 import aQute.bnd.annotation.ProviderType;
 
 /**
- * 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.
+ * The client context can be used by client code to check whether a specific
+ * feature is enable.
+ * <p>
+ * Prepared {@code ClientContext} instances are available through the
+ * {@link Features} service. Consumers of this interface are not expected to
+ * implent it.
  */
 @ProviderType
 public interface ClientContext {
 
     /**
-     * Returns <code>true</code> if the feature is enabled.
+     * Returns {@code true} if the named feature is enabled.
+     *
+     * @param featureName The name of the feature.
+     * @return {@code true} if the named feature is enabled. {@code false} is
+     *         returned if the named feature is not enabled, is not known or the
+     *         {@code featureName} parameter is {@code null} or an empty String.
      */
     boolean isEnabled(String featureName);
 
     /**
-     * Returns a list of all enabled features
-     * @return The list of features, the list might be empty.
+     * Returns a possibly empty collection of enabled {@link Feature} instances.
+     *
+     * @return The collection of enabled {@link Feature} instances. This
+     *         collection may be empty and is not modifiable.
      */
     Collection<Feature> getEnabledFeatures();
 }
diff --git a/src/main/java/org/apache/sling/featureflags/ExecutionContext.java b/src/main/java/org/apache/sling/featureflags/ExecutionContext.java
index d01bd67..14d684d 100644
--- a/src/main/java/org/apache/sling/featureflags/ExecutionContext.java
+++ b/src/main/java/org/apache/sling/featureflags/ExecutionContext.java
@@ -18,26 +18,49 @@
  */
 package org.apache.sling.featureflags;
 
-import org.apache.sling.api.SlingHttpServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
 import org.apache.sling.api.resource.ResourceResolver;
 
 import aQute.bnd.annotation.ProviderType;
 
 /**
- * The provider context contains all information that is passed to a
- * {@link Feature} in order to check whether a feature is enabled.
+ * The {@code ExecutionContext} interface provides access to the context for
+ * evaluating whether a feature is enabled or not. Instances of this object are
+ * provided to the {@link Feature#isEnabled(ExecutionContext)} to help
+ * evaluating whether the feature is enabled or not.
+ * <p>
+ * The {@link Features} service {@link ClientContext} generating methods create
+ * an instance of this to collect the enabled {@link Feature} services for the
+ * creation of the {@link ClientContext} instance.
+ * <p>
+ * This object provides access to live data and must only be used to read
+ * information. Modifying content through a {@code ResourceResolver} directly or
+ * indirectly provided by this object is considered inappropriate and faulty
+ * behaviour.
  */
 @ProviderType
 public interface ExecutionContext {
 
     /**
-     * Return the associated request if available
+     * Returns a {@code HttpServletRequest} object to retrieve information which
+     * may influence the decision whether a {@link Feature} is enabled or not.
+     * If a {@code HttpServletRequest} object is not available in the context,
+     * this method may return {@code null}.
+     * <p>
+     * In a Sling request processing context, the {@code HttpServletRequest}
+     * object returned may actually be a {@code SlingHttpServletRequest}.
+     *
      * @return the request or <code>null</code>
      */
-    SlingHttpServletRequest getRequest();
+    HttpServletRequest getRequest();
 
     /**
-     * Return the associated resource resolver.
+     * Returns a {@code ResourceResolver} object to retrieve information which
+     * may influence the decision whether a {@link Feature} is enabled or not.
+     * If a {@code ResourceResolver} object is not available in the context,
+     * this method may return {@code null}.
+     *
      * @return the resource resolver
      */
     ResourceResolver getResourceResolver();
diff --git a/src/main/java/org/apache/sling/featureflags/Feature.java b/src/main/java/org/apache/sling/featureflags/Feature.java
index 5bec8fd..3984df5 100644
--- a/src/main/java/org/apache/sling/featureflags/Feature.java
+++ b/src/main/java/org/apache/sling/featureflags/Feature.java
@@ -18,37 +18,47 @@
  */
 package org.apache.sling.featureflags;
 
-import org.apache.sling.api.adapter.Adaptable;
-
 import aQute.bnd.annotation.ConsumerType;
 
 /**
- * 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 ResourceTypeMapping}</li>
- * </ul>
- *
- * Features are registered as OSGi services.
+ * A feature is defined by its name. Features are registered as OSGi services.
+ * This interface is expected to be implemented by feature providers.
+ * <p>
+ * Feature {@link #getName() names} should be globally unique. If multiple
+ * features have the same name, the feature with the highest service ranking is
+ * accessible through the {@link Features} service and the {@link ClientContext}.
  */
 @ConsumerType
-public interface Feature extends Adaptable {
+public interface Feature {
 
     /**
-     * Checks whether the feature is enabled for the current execution
-     * context.
+     * Checks whether the feature is enabled for the given execution context.
+     * <p>
+     * Multiple calls to this method may but are not required to return the same
+     * value. For example the return value may depend on the time of day, some
+     * random number or some information provided by the given
+     * {@link ExecutionContext}.
+     *
+     * @param context The {@link ExecutionContext} providing a context to
+     *            evaluate whether the feature is enabled or not.
+     * @return {@code true} if this {@code Feature} is enabled in the given
+     *         {@link ExecutionContext}.
      */
     boolean isEnabled(ExecutionContext context);
 
     /**
      * The name of the feature.
+     *
+     * @return The name of this feature which must not be {@code null} or an
+     *         empty string.
      */
     String getName();
 
     /**
      * The description of the feature.
+     *
+     * @return The optional description of this feature, which may be
+     *         {@code null} or an empty string.
      */
     String getDescription();
 }
diff --git a/src/main/java/org/apache/sling/featureflags/Features.java b/src/main/java/org/apache/sling/featureflags/Features.java
index 8b6a810..eb5faac 100644
--- a/src/main/java/org/apache/sling/featureflags/Features.java
+++ b/src/main/java/org/apache/sling/featureflags/Features.java
@@ -18,59 +18,108 @@
  */
 package org.apache.sling.featureflags;
 
-import org.apache.sling.api.SlingHttpServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
 import org.apache.sling.api.resource.ResourceResolver;
 
 import aQute.bnd.annotation.ProviderType;
 
 /**
- * The features service is the central gateway for feature handling.
- * It can be used to query the available features and to create
- * client contexts to be used for enabled feature checking.
+ * The features service is the central gateway for feature handling. It can be
+ * used to query the available features and to create client contexts to be used
+ * for enabled feature checking.
  */
 @ProviderType
 public interface Features {
 
     /**
-     * Get the list of all available feature names. A feature is available
-     * if there is a registered {@link Feature} service.
+     * Get the list of all available (known) feature names.
+     * <p>
+     * Features are known if they are registered as {@link Feature} services or
+     * are configured with OSGi configuration whose factory PID is
+     * {@code org.apache.sling.featureflags.Feature}.
+     *
+     * @return The names of the known features
      */
     String[] getAvailableFeatureNames();
 
     /**
-     * Get the list of all available features. A feature is available
-     * if there is a registered {@link Feature} service.
+     * Get the list of all available (known) features.
+     * <p>
+     * Features are known if they are registered as {@link Feature} services or
+     * are configured with OSGi configuration whose factory PID is
+     * {@code org.apache.sling.featureflags.Feature}.
+     *
+     * @return The known features
      */
     Feature[] getAvailableFeatures();
 
     /**
      * Returns the feature with the given name.
-     * @return The feature or <code>null</code>
+     *
+     * @param name The name of the feature.
+     * @return The feature or <code>null</code> if not known or the name is an
+     *         empty string or {@code null}.
      */
     Feature getFeature(String name);
 
     /**
-     * Checks whether a feature with the given name is available.
-     * A feature is available if there is a registered {@link Feature} service.
+     * Checks whether a feature with the given name is available (known).
+     * <p>
+     * Features are known if they are registered as {@link Feature} services or
+     * are configured with OSGi configuration whose factory PID is
+     * {@code org.apache.sling.featureflags.Feature}.
+     *
+     * @param featureName The name of the feature to check for availability.
+     * @return {@code true} if the named feature is available.
      */
     boolean isAvailable(String featureName);
 
     /**
-     * Returns the current client context.
-     * This method always returns a client context object
+     * Returns the current client context. This method always returns a client
+     * context object.
+     *
      * @return A client context.
      */
     ClientContext getCurrentClientContext();
 
     /**
      * Create a client context for the resource resolver.
-     * @throws IllegalArgumentException If resolver is null
+     * <p>
+     * The {@link ClientContext} is a snapshot of the enablement state of the
+     * features at the time of creation. A change in the feature enablement
+     * state is not reflected in {@link ClientContext} objects created prior to
+     * changing the state.
+     * <p>
+     * The {@link ClientContext} returned is not available through the
+     * {@link #getCurrentClientContext()} method.
+     *
+     * @param resolver The {@code ResourceResolver} to base the
+     *            {@link ClientContext} on.
+     * @return A newly created client context based on the given
+     *         {@code ResourceResolver}.
+     * @throws IllegalArgumentException If {@code resolver} is {@code null}
      */
     ClientContext createClientContext(ResourceResolver resolver);
 
     /**
      * Create a client context for the request.
-     * @throws IllegalArgumentException If request is null
+     * <p>
+     * The {@link ClientContext} is a snapshot of the enablement state of the
+     * features at the time of creation. A change in the feature enablement
+     * state is not reflected in {@link ClientContext} objects created prior to
+     * changing the state.
+     * <p>
+     * The {@link ClientContext} returned is not available through the
+     * {@link #getCurrentClientContext()} method.
+     *
+     * @param request The {@code HttpServletRequest} to base the
+     *            {@link ClientContext} on. If this is a
+     *            {@code SlingHttpServletContext} the {@link ClientContext}'s
+     *            resource resolver is set to the request's resource resolver.
+     * @return A newly created client context based on the given
+     *         {@code HttpServletRequest}.
+     * @throws IllegalArgumentException If {@code request} is {@code null}
      */
-    ClientContext createClientContext(SlingHttpServletRequest request);
+    ClientContext createClientContext(HttpServletRequest request);
 }
diff --git a/src/main/java/org/apache/sling/featureflags/ResourceHiding.java b/src/main/java/org/apache/sling/featureflags/ResourceHiding.java
deleted file mode 100644
index d19fab6..0000000
--- a/src/main/java/org/apache/sling/featureflags/ResourceHiding.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.featureflags;
-
-import org.apache.sling.api.resource.Resource;
-
-import aQute.bnd.annotation.ConsumerType;
-
-/**
- * A {@link Feature} which is hiding resources can be adapted to
- * this service interface.
- */
-@ConsumerType
-public interface ResourceHiding {
-
-    /**
-     * Checks whether a resource should be hidden for a feature.
-     * This check is only executed if {@link Feature#isEnabled(ExecutionContext)}
-     * return true for the given feature/context. The caller of this
-     * method must ensure to call {@link Feature#isEnabled(ExecutionContext)}
-     * before calling this method and only call this method if
-     * {@link Feature#isEnabled(ExecutionContext)} returned <code>true</code>
-     */
-    boolean hideResource(Resource resource);
-}
diff --git a/src/main/java/org/apache/sling/featureflags/ResourceTypeMapping.java b/src/main/java/org/apache/sling/featureflags/ResourceTypeMapping.java
deleted file mode 100644
index a9ca5f4..0000000
--- a/src/main/java/org/apache/sling/featureflags/ResourceTypeMapping.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.featureflags;
-
-import java.util.Map;
-
-import aQute.bnd.annotation.ConsumerType;
-
-/**
- * A {@link Feature} which is mapping resource types can be adapted to
- * this service interface.
- */
-@ConsumerType
-public interface ResourceTypeMapping {
-
-    /**
-     * Returns the resource type mapping for a feature.
-     * This mapping is only executed if {@link Feature#isEnabled(ExecutionContext)}
-     * return true for the given feature/context. The caller of this
-     * method must ensure to call {@link Feature#isEnabled(ExecutionContext)}
-     * before calling this method and only call this method if
-     * {@link Feature#isEnabled(ExecutionContext)} returned <code>true</code>
-     */
-    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 b71ebe4..dc02b25 100644
--- a/src/main/java/org/apache/sling/featureflags/impl/ClientContextImpl.java
+++ b/src/main/java/org/apache/sling/featureflags/impl/ClientContextImpl.java
@@ -21,15 +21,13 @@ package org.apache.sling.featureflags.impl;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import org.apache.sling.api.resource.ResourceDecorator;
 import org.apache.sling.featureflags.ClientContext;
 import org.apache.sling.featureflags.Feature;
 import org.apache.sling.featureflags.ExecutionContext;
-import org.apache.sling.featureflags.ResourceHiding;
-import org.apache.sling.featureflags.ResourceTypeMapping;
 
 /**
  * Implementation of the client context
@@ -38,28 +36,22 @@ public class ClientContextImpl implements ClientContext {
 
     private final ExecutionContext featureContext;
 
-    private final List<Feature> enabledFeatures;
+    private final Map<String, Feature> enabledFeatures;
 
-    private final List<ResourceHiding> hidingFeatures;
+    private final List<ResourceDecorator> resourceDecorators;
 
-    private final Map<String, String> mapperFeatures = new HashMap<String, String>();
-
-    public ClientContextImpl(final ExecutionContext featureContext, final List<Feature> features) {
-        this.enabledFeatures = Collections.unmodifiableList(features);
-        final List<ResourceHiding> hiding = new ArrayList<ResourceHiding>();
-        for(final Feature f : this.enabledFeatures) {
-            final ResourceHiding rh = f.adaptTo(ResourceHiding.class);
-            if ( rh != null ) {
-                hiding.add(rh);
-            }
-            final ResourceTypeMapping rm = f.adaptTo(ResourceTypeMapping.class);
-            if ( rm != null ) {
-                final Map<String, String> mapping = rm.getResourceTypeMapping();
-                mapperFeatures.putAll(mapping);
+    public ClientContextImpl(final ExecutionContext featureContext, final Map<String, Feature> features) {
+        ArrayList<ResourceDecorator> resourceDecorators = new ArrayList<ResourceDecorator>(features.size());
+        for (final Feature f : features.values()) {
+            if (f instanceof ResourceDecorator) {
+                resourceDecorators.add((ResourceDecorator) f);
             }
         }
-        this.hidingFeatures = hiding;
+        resourceDecorators.trimToSize();
+
         this.featureContext = featureContext;
+        this.enabledFeatures = Collections.unmodifiableMap(features);
+        this.resourceDecorators = Collections.unmodifiableList(resourceDecorators);
     }
 
     public ExecutionContext getFeatureContext() {
@@ -68,24 +60,15 @@ public class ClientContextImpl implements ClientContext {
 
     @Override
     public boolean isEnabled(final String featureName) {
-        for(final Feature f : this.enabledFeatures) {
-            if ( featureName.equals(f.getName()) ) {
-                return true;
-            }
-        }
-        return false;
+        return this.enabledFeatures.get(featureName) != null;
     }
 
     @Override
     public Collection<Feature> getEnabledFeatures() {
-        return this.enabledFeatures;
-    }
-
-    public Collection<ResourceHiding> getHidingFeatures() {
-        return this.hidingFeatures;
+        return this.enabledFeatures.values();
     }
 
-    public Map<String, String> getResourceTypeMapping() {
-        return this.mapperFeatures;
+    public List<ResourceDecorator> getResourceDecorators() {
+        return this.resourceDecorators;
     }
 }
diff --git a/src/main/java/org/apache/sling/featureflags/impl/ConfiguredFeature.java b/src/main/java/org/apache/sling/featureflags/impl/ConfiguredFeature.java
new file mode 100644
index 0000000..1f4db86
--- /dev/null
+++ b/src/main/java/org/apache/sling/featureflags/impl/ConfiguredFeature.java
@@ -0,0 +1,105 @@
+/*
+ * 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.featureflags.impl;
+
+import java.util.Map;
+
+import javax.servlet.ServletRequest;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.featureflags.ExecutionContext;
+import org.apache.sling.featureflags.Feature;
+import org.osgi.framework.Constants;
+
+@Component(
+        name = "org.apache.sling.featureflags.Feature",
+        metatype = true,
+        configurationFactory = true,
+        policy = ConfigurationPolicy.REQUIRE,
+        label = "Statically Configured Feature",
+        description = "Allows for the definition of statically configured features which are defined and enabled through OSGi configuration")
+@Service
+public class ConfiguredFeature implements Feature {
+
+    private static final String PROP_FEATURE = "feature";
+
+    @Property(label = "Name", description = "Short name of this feature. This name is used "
+        + "to refer to this feature when checking for it to be enabled or not. This "
+        + "property is required and defaults to a name derived from the feature's class "
+        + "name and object identity. It is strongly recommended to define a useful and unique for the feature")
+    private static final String NAME = "name";
+
+    @Property(label = "Description", description = "Description for the feature. The "
+        + "intent is to descibe the behaviour of the application if this feature would be "
+        + "enabled. It is recommended to define this property. The default value is the value of the name property.")
+    private static final String DESCRIPTION = "description";
+
+    @Property(boolValue = false, label = "Enabled", description = "Boolean flag indicating whether the feature is "
+        + "enabled or not by this configuration")
+    private static final String ENABLED = "enabled";
+
+    private String name;
+
+    private String description;
+
+    private boolean enabled;
+
+    @Activate
+    private void activate(final Map<String, Object> configuration) {
+        final String pid = PropertiesUtil.toString(configuration.get(Constants.SERVICE_PID), getClass().getName() + "$"
+            + System.identityHashCode(this));
+        this.name = PropertiesUtil.toString(configuration.get(NAME), pid);
+        this.description = PropertiesUtil.toString(configuration.get(DESCRIPTION), this.name);
+        this.enabled = PropertiesUtil.toBoolean(configuration.get(ENABLED), false);
+    }
+
+    @Override
+    public boolean isEnabled(ExecutionContext context) {
+
+        // Request Parameter Override
+        ServletRequest request = context.getRequest();
+        if (request != null) {
+            String[] features = request.getParameterValues(PROP_FEATURE);
+            if (features != null) {
+                for (String feature : features) {
+                    if (this.name.equals(feature)) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return this.enabled;
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    @Override
+    public String getDescription() {
+        return this.description;
+    }
+}
diff --git a/src/main/java/org/apache/sling/featureflags/impl/CurrentClientContextFilter.java b/src/main/java/org/apache/sling/featureflags/impl/CurrentClientContextFilter.java
index 9399666..da6287f 100644
--- a/src/main/java/org/apache/sling/featureflags/impl/CurrentClientContextFilter.java
+++ b/src/main/java/org/apache/sling/featureflags/impl/CurrentClientContextFilter.java
@@ -26,45 +26,38 @@ import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
-
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Property;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.featureflags.ClientContext;
 
 /**
- * This general servlet filter sets the current client context to the
- * current request.
+ * This general servlet filter sets the current client context to the current
+ * request.
  */
-@Component
-@Service(value=Filter.class)
-@Property(name="pattern", value="/.*")
 public class CurrentClientContextFilter implements Filter {
 
-    @Reference
-    private FeatureManager manager;
+    private final FeatureManager featureManager;
+
+    public CurrentClientContextFilter(final FeatureManager featureManager) {
+        this.featureManager = featureManager;
+    }
 
     @Override
-    public void doFilter(final ServletRequest req, final ServletResponse res,
-            final FilterChain chain)
-    throws IOException, ServletException {
-        if ( req instanceof SlingHttpServletRequest ) {
-            manager.setCurrentClientContext((SlingHttpServletRequest)req);
-        }
+    public void init(final FilterConfig config) {
+        // nothing to do
+    }
+
+    @Override
+    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
+            throws IOException, ServletException {
+
+        ClientContext current = this.featureManager.setCurrentClientContext(req);
         try {
             chain.doFilter(req, res);
         } finally {
-            manager.unsetCurrentClientContext();
+            this.featureManager.unsetCurrentClientContext(current);
         }
     }
 
     @Override
-    public void init(final FilterConfig config) throws ServletException {
-        // nothing to do
-    }
-
-    @Override
     public void destroy() {
         // nothing to do
     }
diff --git a/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java b/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java
index 555575f..cfd673a 100644
--- a/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java
+++ b/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java
@@ -18,6 +18,8 @@
  */
 package org.apache.sling.featureflags.impl;
 
+import javax.servlet.http.HttpServletRequest;
+
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.featureflags.ExecutionContext;
@@ -29,20 +31,22 @@ public class ExecutionContextImpl implements ExecutionContext {
 
     private final ResourceResolver resourceResolver;
 
-    private final SlingHttpServletRequest request;
+    private final HttpServletRequest request;
 
     public ExecutionContextImpl(final ResourceResolver resourceResolver) {
         this.request = null;
         this.resourceResolver = resourceResolver;
     }
 
-    public ExecutionContextImpl(final SlingHttpServletRequest request) {
+    public ExecutionContextImpl(final HttpServletRequest request) {
         this.request = request;
-        this.resourceResolver = request.getResourceResolver();
+        this.resourceResolver = (request instanceof SlingHttpServletRequest)
+                ? ((SlingHttpServletRequest) request).getResourceResolver()
+                : null;
     }
 
     @Override
-    public SlingHttpServletRequest getRequest() {
+    public HttpServletRequest getRequest() {
         return this.request;
     }
 
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 fd601f6..61b5641 100644
--- a/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java
+++ b/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java
@@ -22,51 +22,116 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
-import java.util.TreeMap;
 
+import javax.servlet.Filter;
+import javax.servlet.Servlet;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
 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.ResourceDecorator;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.featureflags.ClientContext;
+import org.apache.sling.featureflags.ExecutionContext;
 import org.apache.sling.featureflags.Feature;
 import org.apache.sling.featureflags.Features;
-import org.apache.sling.featureflags.ExecutionContext;
+import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * This service implements the feature handling.
- * It keeps track of all {@link Feature} services.
+ * This service implements the feature handling. It keeps track of all
+ * {@link Feature} services.
  */
 @Component
-@Reference(name="feature",
-           cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,
-           policy=ReferencePolicy.DYNAMIC,
-           referenceInterface=Feature.class)
-public class FeatureManager implements Features {
+@Reference(
+        name = "feature",
+        cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+        policy = ReferencePolicy.DYNAMIC,
+        referenceInterface = Feature.class)
+public class FeatureManager {
 
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
+    private final ThreadLocal<ClientContext> perThreadClientContext = new ThreadLocal<ClientContext>();
+
+    private final ClientContext defaultClientContext = new ClientContext() {
+        @Override
+        public boolean isEnabled(final String featureName) {
+            return false;
+        }
+
+        @Override
+        public Collection<Feature> getEnabledFeatures() {
+            return Collections.emptyList();
+        }
+    };
+
     private final Map<String, List<FeatureDescription>> allFeatures = new HashMap<String, List<FeatureDescription>>();
 
-    private Map<String, FeatureDescription> activeFeatures = new TreeMap<String, FeatureDescription>();
+    private Map<String, Feature> activeFeatures = Collections.emptyMap();
+
+    private List<ServiceRegistration> services;
+
+    @SuppressWarnings("serial")
+    @Activate
+    private void activate(BundleContext bundleContext) {
+        ArrayList<ServiceRegistration> services = new ArrayList<ServiceRegistration>();
+        services.add(bundleContext.registerService(Features.class.getName(), new FeaturesImpl(this), null));
+        services.add(bundleContext.registerService(ResourceDecorator.class.getName(),
+            new FeatureResourceDecorator(this), null));
+        services.add(bundleContext.registerService(Servlet.class.getName(), new FeatureWebConsolePlugin(this),
+            new Hashtable<String, Object>() {
+                {
+                    put("felix.webconsole.label", "features");
+                    put("felix.webconsole.title", "Features");
+                    put("felix.webconsole.category", "Sling");
+                }
+            }));
+        services.add(bundleContext.registerService(Filter.class.getName(), new CurrentClientContextFilter(this),
+            new Hashtable<String, Object>() {
+                {
+                    put("sling.filter.scope", "REQUEST");
+                    put("service.ranking", Integer.MIN_VALUE);
+                }
+            }));
+        this.services = services;
+    }
 
-    /**
-     * Bind a new feature
-     */
-    protected void bindFeature(final Feature f, final Map<String, Object> props) {
-        synchronized ( this.allFeatures ) {
+    @Deactivate
+    private void deactivate() {
+        if (this.services != null) {
+            for (ServiceRegistration service : this.services) {
+                if (service != null) {
+                    service.unregister();
+                }
+            }
+            this.services.clear();
+            this.services = null;
+        }
+    }
+
+    //--- Feature binding
+
+    // bind method for Feature services
+    @SuppressWarnings("unused")
+    private void bindFeature(final Feature f, final Map<String, Object> props) {
+        synchronized (this.allFeatures) {
             final String name = f.getName();
             final FeatureDescription info = new FeatureDescription(f, props);
 
             List<FeatureDescription> candidates = this.allFeatures.get(name);
-            if ( candidates == null ) {
+            if (candidates == null) {
                 candidates = new ArrayList<FeatureDescription>();
                 this.allFeatures.put(name, candidates);
             }
@@ -77,18 +142,17 @@ public class FeatureManager implements Features {
         }
     }
 
-    /**
-     * Unbind a feature
-     */
-    protected void unbindFeature(final Feature f, final Map<String, Object> props) {
-        synchronized ( this.allFeatures ) {
+    // unbind method for Feature services
+    @SuppressWarnings("unused")
+    private void unbindFeature(final Feature f, final Map<String, Object> props) {
+        synchronized (this.allFeatures) {
             final String name = f.getName();
             final FeatureDescription info = new FeatureDescription(f, props);
 
             final List<FeatureDescription> candidates = this.allFeatures.get(name);
-            if ( candidates != null ) { // sanity check
+            if (candidates != null) { // sanity check
                 candidates.remove(info);
-                if ( candidates.size() == 0 ) {
+                if (candidates.size() == 0) {
                     this.allFeatures.remove(name);
                 }
             }
@@ -96,56 +160,50 @@ public class FeatureManager implements Features {
         }
     }
 
+    // calculates map of active features (eliminating Feature name
+    // collisions). Must be called while synchronized on this.allFeatures
     private void calculateActiveProviders() {
-        final Map<String, FeatureDescription> activeMap = new TreeMap<String, FeatureDescription>();
-        for(final Map.Entry<String, List<FeatureDescription>> entry : this.allFeatures.entrySet()) {
+        final Map<String, Feature> activeMap = new HashMap<String, Feature>();
+        for (final Map.Entry<String, List<FeatureDescription>> entry : this.allFeatures.entrySet()) {
             final FeatureDescription desc = entry.getValue().get(0);
-
-            activeMap.put(entry.getKey(), desc);
-            if ( entry.getValue().size() > 1 ) {
+            activeMap.put(entry.getKey(), desc.feature);
+            if (entry.getValue().size() > 1) {
                 logger.warn("More than one feature service for feature {}", entry.getKey());
             }
         }
         this.activeFeatures = activeMap;
     }
 
-    private final ThreadLocal<ClientContextImpl> perThreadClientContext = new ThreadLocal<ClientContextImpl>();
-
-    private final ClientContext defaultClientContext = new ClientContext() {
-
-        @Override
-        public boolean isEnabled(final String featureName) {
-            return false;
-        }
-
-        @Override
-        public Collection<Feature> getEnabledFeatures() {
-            return Collections.emptyList();
-        }
-    };
+    //--- Client Context management and access
 
-    @Override
-    public ClientContext getCurrentClientContext() {
+    ClientContext getCurrentClientContext() {
         ClientContext result = perThreadClientContext.get();
-        if ( result == null ) {
+        if (result == null) {
             result = defaultClientContext;
         }
         return result;
     }
 
-    public void setCurrentClientContext(final SlingHttpServletRequest request) {
-        final ExecutionContext providerContext = new ExecutionContextImpl(request);
-        final ClientContextImpl ctx = this.createClientContext(providerContext);
-        perThreadClientContext.set(ctx);
+    ClientContext setCurrentClientContext(final ServletRequest request) {
+        final ClientContext current = perThreadClientContext.get();
+        if (request instanceof HttpServletRequest) {
+            final ExecutionContext providerContext = new ExecutionContextImpl((HttpServletRequest) request);
+            final ClientContextImpl ctx = this.createClientContext(providerContext);
+            perThreadClientContext.set(ctx);
+        }
+        return current;
     }
 
-    public void unsetCurrentClientContext() {
-        perThreadClientContext.remove();
+    void unsetCurrentClientContext(final ClientContext previous) {
+        if (previous != null) {
+            perThreadClientContext.set(previous);
+        } else {
+            perThreadClientContext.remove();
+        }
     }
 
-    @Override
-    public ClientContext createClientContext(final ResourceResolver resolver) {
-        if ( resolver == null ) {
+    ClientContext createClientContext(final ResourceResolver resolver) {
+        if (resolver == null) {
             throw new IllegalArgumentException("Resolver must not be null.");
         }
         final ExecutionContext providerContext = new ExecutionContextImpl(resolver);
@@ -153,9 +211,8 @@ public class FeatureManager implements Features {
         return ctx;
     }
 
-    @Override
-    public ClientContext createClientContext(final SlingHttpServletRequest request) {
-        if ( request == null ) {
+    ClientContext createClientContext(final HttpServletRequest request) {
+        if (request == null) {
             throw new IllegalArgumentException("Request must not be null.");
         }
         final ExecutionContext providerContext = new ExecutionContextImpl(request);
@@ -164,75 +221,64 @@ public class FeatureManager implements Features {
     }
 
     private ClientContextImpl createClientContext(final ExecutionContext providerContext) {
-        final List<Feature> enabledFeatures = new ArrayList<Feature>();
-
-        for(final Map.Entry<String, FeatureDescription> entry : this.activeFeatures.entrySet()) {
-            final Feature f = entry.getValue().feature;
-
-            if ( entry.getValue().feature.isEnabled(providerContext) ) {
-                enabledFeatures.add(f);
+        final Map<String, Feature> enabledFeatures = new HashMap<String, Feature>();
+        for (final Map.Entry<String, Feature> entry : this.activeFeatures.entrySet()) {
+            if (entry.getValue().isEnabled(providerContext)) {
+                enabledFeatures.put(entry.getKey(), entry.getValue());
             }
         }
 
-        final ClientContextImpl ctx = new ClientContextImpl(providerContext, enabledFeatures);
-        return ctx;
+        return new ClientContextImpl(providerContext, enabledFeatures);
     }
 
-    @Override
-    public Feature[] getAvailableFeatures() {
-        final List<Feature> result = new ArrayList<Feature>();
-        for(final Map.Entry<String, FeatureDescription> entry : this.activeFeatures.entrySet()) {
-            final Feature f = entry.getValue().feature;
-            result.add(f);
-        }
-        return result.toArray(new Feature[result.size()]);
+    //--- Feature access
+
+    Feature[] getAvailableFeatures() {
+        final Map<String, Feature> activeFeatures = this.activeFeatures;
+        return activeFeatures.values().toArray(new Feature[activeFeatures.size()]);
     }
 
-    @Override
-    public Feature getFeature(final String name) {
-        final FeatureDescription desc = this.activeFeatures.get(name);
-        if ( desc != null ) {
-            return desc.feature;
-        }
-        return null;
+    Feature getFeature(final String name) {
+        return this.activeFeatures.get(name);
     }
 
-    @Override
-    public String[] getAvailableFeatureNames() {
-        return this.activeFeatures.keySet().toArray(new String[this.activeFeatures.size()]);
+    String[] getAvailableFeatureNames() {
+        final Map<String, Feature> activeFeatures = this.activeFeatures;
+        return activeFeatures.keySet().toArray(new String[activeFeatures.size()]);
     }
 
-    @Override
-    public boolean isAvailable(final String featureName) {
+    boolean isAvailable(final String featureName) {
         return this.activeFeatures.containsKey(featureName);
     }
 
     /**
-     * Internal class caching some feature meta data like service id and ranking.
+     * Internal class caching some feature meta data like service id and
+     * ranking.
      */
     private final static class FeatureDescription implements Comparable<FeatureDescription> {
 
         public final int ranking;
+
         public final long serviceId;
+
         public final Feature feature;
 
-        public FeatureDescription(final Feature feature,
-                final Map<String, Object> props) {
+        public FeatureDescription(final Feature feature, final Map<String, Object> props) {
             this.feature = feature;
             final Object sr = props.get(Constants.SERVICE_RANKING);
-            if ( sr == null || !(sr instanceof Integer)) {
-                this.ranking = 0;
+            if (sr instanceof Integer) {
+                this.ranking = (Integer) sr;
             } else {
-                this.ranking = (Integer)sr;
+                this.ranking = 0;
             }
-            this.serviceId = (Long)props.get(Constants.SERVICE_ID);
+            this.serviceId = (Long) props.get(Constants.SERVICE_ID);
         }
 
         @Override
         public int compareTo(final FeatureDescription o) {
-            if ( this.ranking < o.ranking ) {
+            if (this.ranking < o.ranking) {
                 return 1;
-            } else if (this.ranking > o.ranking ) {
+            } else if (this.ranking > o.ranking) {
                 return -1;
             }
             // If ranks are equal, then sort by service id in descending order.
@@ -241,8 +287,8 @@ public class FeatureManager implements Features {
 
         @Override
         public boolean equals(final Object obj) {
-            if ( obj instanceof FeatureDescription ) {
-                return ((FeatureDescription)obj).serviceId == this.serviceId;
+            if (obj instanceof FeatureDescription) {
+                return ((FeatureDescription) obj).serviceId == this.serviceId;
             }
             return false;
         }
diff --git a/src/main/java/org/apache/sling/featureflags/impl/ResourceDecoratorImpl.java b/src/main/java/org/apache/sling/featureflags/impl/FeatureResourceDecorator.java
similarity index 58%
rename from src/main/java/org/apache/sling/featureflags/impl/ResourceDecoratorImpl.java
rename to src/main/java/org/apache/sling/featureflags/impl/FeatureResourceDecorator.java
index e2f818d..4985ab0 100644
--- a/src/main/java/org/apache/sling/featureflags/impl/ResourceDecoratorImpl.java
+++ b/src/main/java/org/apache/sling/featureflags/impl/FeatureResourceDecorator.java
@@ -20,46 +20,34 @@ package org.apache.sling.featureflags.impl;
 
 import javax.servlet.http.HttpServletRequest;
 
-import org.apache.felix.scr.annotations.Component;
-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.api.resource.ResourceDecorator;
-import org.apache.sling.api.resource.ResourceWrapper;
 import org.apache.sling.featureflags.ClientContext;
 
 /**
  * Resource decorator implementing the resource type mapping
  */
-@Component
-@Service(value=ResourceDecorator.class)
-public class ResourceDecoratorImpl implements ResourceDecorator {
+public class FeatureResourceDecorator implements ResourceDecorator {
 
-    @Reference
-    private FeatureManager manager;
+    private final FeatureManager manager;
+
+    FeatureResourceDecorator(final FeatureManager manager) {
+        this.manager = manager;
+    }
 
     @Override
     public Resource decorate(final Resource resource) {
+        Resource result = resource;
         final ClientContext info = manager.getCurrentClientContext();
-        if ( info != null ) {
-            final String resourceType = resource.getResourceType();
-            final String overwriteType = ((ClientContextImpl)info).getResourceTypeMapping().get(resourceType);
-            if ( overwriteType != null ) {
-                return new ResourceWrapper(resource) {
-
-                    @Override
-                    public String getResourceType() {
-                        return overwriteType;
-                    }
-
-                    @Override
-                    public String getResourceSuperType() {
-                        return resourceType;
-                    }
-                };
+        if (info instanceof ClientContextImpl) {
+            for (ResourceDecorator rd : ((ClientContextImpl) info).getResourceDecorators()) {
+                Resource r = rd.decorate(resource);
+                if (r != null) {
+                    result = r;
+                }
             }
         }
-        return resource;
+        return result;
     }
 
     @Override
diff --git a/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java b/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java
new file mode 100644
index 0000000..ff2b8d7
--- /dev/null
+++ b/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java
@@ -0,0 +1,74 @@
+/*
+ * 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.featureflags.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.featureflags.ExecutionContext;
+import org.apache.sling.featureflags.Feature;
+
+@SuppressWarnings("serial")
+public class FeatureWebConsolePlugin extends HttpServlet {
+
+    private final FeatureManager featureManager;
+
+    FeatureWebConsolePlugin(final FeatureManager featureManager) {
+        this.featureManager = featureManager;
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        final PrintWriter pw = resp.getWriter();
+        final Feature[] features = this.featureManager.getAvailableFeatures();
+        if (features == null || features.length == 0) {
+            pw.println("<p class='statline ui-state-highlight'>No Features currently defined</p>");
+        } else {
+            pw.printf("<p class='statline ui-state-highlight'>%d Feature(s) currently defined</p>%n", features.length);
+            pw.println("<table class='nicetable'>");
+            pw.println("<tr><th>Name</th><th>Description</th><th>Enabled</th></tr>");
+            final ExecutionContext ctx = createContext(req);
+            for (final Feature feature : features) {
+                pw.printf("<tr><td>%s</td><td>%s</td><td>%s</td></tr>%n", feature.getName(), feature.getDescription(),
+                    feature.isEnabled(ctx));
+            }
+            pw.println("</table>");
+        }
+    }
+
+    private ExecutionContext createContext(final HttpServletRequest req) {
+        return new ExecutionContext() {
+
+            @Override
+            public ResourceResolver getResourceResolver() {
+                return null;
+            }
+
+            @Override
+            public HttpServletRequest getRequest() {
+                return req;
+            }
+        };
+    }
+}
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 e0e836e..eb67dd6 100644
--- a/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java
+++ b/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java
@@ -18,10 +18,8 @@
  */
 package org.apache.sling.featureflags.impl;
 
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.api.SlingHttpServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.featureflags.ClientContext;
 import org.apache.sling.featureflags.Feature;
@@ -30,12 +28,13 @@ import org.apache.sling.featureflags.Features;
 /**
  * This is a wrapper around the internal feature manager.
  */
-@Component
-@Service(value=Features.class)
 public class FeaturesImpl implements Features {
 
-    @Reference
-    private FeatureManager manager;
+    private final FeatureManager manager;
+
+    FeaturesImpl(final FeatureManager manager) {
+        this.manager = manager;
+    }
 
     @Override
     public String[] getAvailableFeatureNames() {
@@ -68,7 +67,7 @@ public class FeaturesImpl implements Features {
     }
 
     @Override
-    public ClientContext createClientContext(final SlingHttpServletRequest request) {
+    public ClientContext createClientContext(final HttpServletRequest request) {
         return this.manager.createClientContext(request);
     }
 }
diff --git a/src/main/java/org/apache/sling/featureflags/impl/ResourceAccessImpl.java b/src/main/java/org/apache/sling/featureflags/impl/ResourceAccessImpl.java
deleted file mode 100644
index 8772cf3..0000000
--- a/src/main/java/org/apache/sling/featureflags/impl/ResourceAccessImpl.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.featureflags.impl;
-
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Property;
-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;
-
-/**
- * Resource access gate implementing the hiding of resources.
- */
-@Component
-@Service(value=ResourceAccessGate.class)
-@Property(name=ResourceAccessGate.CONTEXT, value=ResourceAccessGate.APPLICATION_CONTEXT)
-public class ResourceAccessImpl
-    extends AllowingResourceAccessGate
-    implements ResourceAccessGate {
-
-    @Reference
-    private FeatureManager manager;
-
-    @Override
-    public GateResult canRead(final Resource resource) {
-        boolean available = true;
-        final ClientContext info = manager.getCurrentClientContext();
-        if ( info != null ) {
-            for(final ResourceHiding f : ((ClientContextImpl)info).getHidingFeatures() ) {
-                available = !f.hideResource(resource);
-                if ( !available) {
-                    break;
-                }
-            }
-        }
-        return (available ? GateResult.DONTCARE : GateResult.DENIED);
-    }
-}
diff --git a/src/main/java/org/apache/sling/featureflags/package-info.java b/src/main/java/org/apache/sling/featureflags/package-info.java
index 90264d0..f1fb45b 100644
--- a/src/main/java/org/apache/sling/featureflags/package-info.java
+++ b/src/main/java/org/apache/sling/featureflags/package-info.java
@@ -18,10 +18,48 @@
  */
 
 /**
- * Provides a service to interface which may be implemented by applications
- * to get notified on cluster topology changes.
+ * The <i>Feature Flags</i> feature allows applications to dynamically
+ * provide features to clients and consumers depending on various criteria such as
+ * <ul>
+ * <li>Time of Day</li>
+ * <li>Static Configuration</li>
+ * <li>Request Parameter</li>
+ * <li>Some Resource</li>
+ * </ul>
+ * <p>
+ * Feature flag support consists of two parts: The feature flag itself represented
+ * by the {@link org.apache.sling.featureflags.Feature Feature} interface and the
+ * the application providing a feature guarded by a feature flag. Such applications
+ * make use of the {@link org.apache.sling.featureflags.Features Features} service to
+ * query feature flags.
+ * <p>
+ * Feature flags can be provided by registering
+ * {@link org.apache.sling.featureflags.Feature Feature} services. Alternatively
+ * feature flags can be provided by factory configuration with factory PID
+ * {@code org.apache.sling.featureflags.Feature} as follows:
+ * <table>
+ *  <tr>
+ *      <td>{@code name}</td>
+ *      <td>Short name of this feature. This name is used to refer to this feature
+ *          when checking for it to be enabled or not. This property is required
+ *          and defaults to a name derived from the feature's class name and object
+ *          identity. It is strongly recommended to define a useful and unique for the feature</td>
+ *  </tr>
+ *  <tr>
+ *      <td>{@code description}</td>
+ *      <td>Description for the feature. The intent is to descibe the behaviour
+ *          of the application if this feature would be enabled. It is recommended
+ *          to define this property. The default value is the value of the name property.</td>
+ *  </tr>
+ *  <tr>
+ *      <td>{@code enabled}</td>
+ *      <td>Boolean flag indicating whether the feature is enabled or not by
+ *          this configuration</td>
+ *  </tr>
+ * </table>
  *
  * @version 1.0
+ * @see <a href="http://sling.apache.org/documentation/the-sling-engine/featureflags.html">Feature Flags</a>
  */
 @Version("1.0")
 package org.apache.sling.featureflags;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.