You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by fm...@apache.org on 2014/01/27 09:58:11 UTC

svn commit: r1561613 - in /sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags: ./ impl/

Author: fmeschbe
Date: Mon Jan 27 08:58:11 2014
New Revision: 1561613

URL: http://svn.apache.org/r1561613
Log:
Multiple Enhancements:

* Cleanup the API
* Base ClientContext and ExecutionContext on HttpServletReequest
* Add(/improve JavaDoc
* Add WebConsolePlugin
* Add (factory) configuration support to define features

Added:
    sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java   (with props)
Modified:
    sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/ClientContext.java
    sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/ExecutionContext.java
    sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/Feature.java
    sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/Features.java
    sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java
    sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java
    sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java
    sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/package-info.java

Modified: sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/ClientContext.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/ClientContext.java?rev=1561613&r1=1561612&r2=1561613&view=diff
==============================================================================
--- sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/ClientContext.java (original)
+++ sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/ClientContext.java Mon Jan 27 08:58:11 2014
@@ -23,9 +23,12 @@ 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 {
@@ -41,8 +44,10 @@ public interface ClientContext {
     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();
 }

Modified: sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/ExecutionContext.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/ExecutionContext.java?rev=1561613&r1=1561612&r2=1561613&view=diff
==============================================================================
--- sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/ExecutionContext.java (original)
+++ sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/ExecutionContext.java Mon Jan 27 08:58:11 2014
@@ -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();

Modified: sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/Feature.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/Feature.java?rev=1561613&r1=1561612&r2=1561613&view=diff
==============================================================================
--- sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/Feature.java (original)
+++ sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/Feature.java Mon Jan 27 08:58:11 2014
@@ -21,26 +21,44 @@ package org.apache.sling.featureflags;
 import aQute.bnd.annotation.ConsumerType;
 
 /**
- * A feature is defined by its name.
- *
- * 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 {
 
     /**
-     * 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();
 }

Modified: sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/Features.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/Features.java?rev=1561613&r1=1561612&r2=1561613&view=diff
==============================================================================
--- sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/Features.java (original)
+++ sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/Features.java Mon Jan 27 08:58:11 2014
@@ -18,69 +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 {
 
     /**
-     * Resource property of type String or String[] listing the features
-     * applicable to a resource.
+     * Get the list of all available (known) feature names.
      * <p>
-     * If the ResourceResolver supports feature flags, the respective resource
-     * property is expected to be copied into the ResourceMetadata as a String[]
-     * property of this name.
-     */
-    String FEATURE_PROPERTY = "sling:features";
-
-    /**
-     * Get the list of all available feature names. A feature is available
-     * if there is a registered {@link Feature} service.
+     * 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);
 }

Modified: sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java?rev=1561613&r1=1561612&r2=1561613&view=diff
==============================================================================
--- sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java (original)
+++ sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java Mon Jan 27 08:58:11 2014
@@ -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 implem
 
     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;
     }
 

Modified: sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java?rev=1561613&r1=1561612&r2=1561613&view=diff
==============================================================================
--- sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java (original)
+++ sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java Mon Jan 27 08:58:11 2014
@@ -22,17 +22,20 @@ 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.Servlet;
+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;
@@ -46,14 +49,15 @@ 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)
+@Reference(
+        name = "feature",
+        cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+        policy = ReferencePolicy.DYNAMIC,
+        referenceInterface = Feature.class)
 public class FeatureManager {
 
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
@@ -66,15 +70,31 @@ public class FeatureManager {
 
     private ServiceRegistration resourceDecorator;
 
+    private ServiceRegistration featureWebConsolePlugin;
+
+    @SuppressWarnings("serial")
     @Activate
     private void activate(BundleContext bundleContext) {
         this.featuresService = bundleContext.registerService(Features.class.getName(), new FeaturesImpl(this), null);
         this.resourceDecorator = bundleContext.registerService(ResourceDecorator.class.getName(),
             new FeatureResourceDecorator(this), null);
+        this.featureWebConsolePlugin = 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");
+                }
+            });
     }
 
     @Deactivate
     private void deactivate() {
+        if (this.featureWebConsolePlugin != null) {
+            this.featureWebConsolePlugin.unregister();
+            this.featureWebConsolePlugin = null;
+        }
+
         if (this.featuresService != null) {
             this.featuresService.unregister();
             this.featuresService = null;
@@ -86,16 +106,13 @@ public class FeatureManager {
         }
     }
 
-    /**
-     * Bind a new feature
-     */
     protected void bindFeature(final Feature f, final Map<String, Object> props) {
-        synchronized ( this.allFeatures ) {
+        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);
             }
@@ -106,18 +123,15 @@ public class FeatureManager {
         }
     }
 
-    /**
-     * Unbind a feature
-     */
     protected void unbindFeature(final Feature f, final Map<String, Object> props) {
-        synchronized ( this.allFeatures ) {
+        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);
                 }
             }
@@ -127,11 +141,11 @@ public class FeatureManager {
 
     private void calculateActiveProviders() {
         final Map<String, FeatureDescription> activeMap = new TreeMap<String, FeatureDescription>();
-        for(final Map.Entry<String, List<FeatureDescription>> entry : this.allFeatures.entrySet()) {
+        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 ) {
+            if (entry.getValue().size() > 1) {
                 logger.warn("More than one feature service for feature {}", entry.getKey());
             }
         }
@@ -155,13 +169,13 @@ public class FeatureManager {
 
     public ClientContext getCurrentClientContext() {
         ClientContext result = perThreadClientContext.get();
-        if ( result == null ) {
+        if (result == null) {
             result = defaultClientContext;
         }
         return result;
     }
 
-    public void setCurrentClientContext(final SlingHttpServletRequest request) {
+    public void setCurrentClientContext(final HttpServletRequest request) {
         final ExecutionContext providerContext = new ExecutionContextImpl(request);
         final ClientContextImpl ctx = this.createClientContext(providerContext);
         perThreadClientContext.set(ctx);
@@ -172,7 +186,7 @@ public class FeatureManager {
     }
 
     public ClientContext createClientContext(final ResourceResolver resolver) {
-        if ( resolver == null ) {
+        if (resolver == null) {
             throw new IllegalArgumentException("Resolver must not be null.");
         }
         final ExecutionContext providerContext = new ExecutionContextImpl(resolver);
@@ -180,8 +194,8 @@ public class FeatureManager {
         return ctx;
     }
 
-    public ClientContext createClientContext(final SlingHttpServletRequest request) {
-        if ( request == null ) {
+    public ClientContext createClientContext(final HttpServletRequest request) {
+        if (request == null) {
             throw new IllegalArgumentException("Request must not be null.");
         }
         final ExecutionContext providerContext = new ExecutionContextImpl(request);
@@ -192,10 +206,10 @@ public class FeatureManager {
     private ClientContextImpl createClientContext(final ExecutionContext providerContext) {
         final List<Feature> enabledFeatures = new ArrayList<Feature>();
 
-        for(final Map.Entry<String, FeatureDescription> entry : this.activeFeatures.entrySet()) {
+        for (final Map.Entry<String, FeatureDescription> entry : this.activeFeatures.entrySet()) {
             final Feature f = entry.getValue().feature;
 
-            if ( entry.getValue().feature.isEnabled(providerContext) ) {
+            if (entry.getValue().feature.isEnabled(providerContext)) {
                 enabledFeatures.add(f);
             }
         }
@@ -206,7 +220,7 @@ public class FeatureManager {
 
     public Feature[] getAvailableFeatures() {
         final List<Feature> result = new ArrayList<Feature>();
-        for(final Map.Entry<String, FeatureDescription> entry : this.activeFeatures.entrySet()) {
+        for (final Map.Entry<String, FeatureDescription> entry : this.activeFeatures.entrySet()) {
             final Feature f = entry.getValue().feature;
             result.add(f);
         }
@@ -215,7 +229,7 @@ public class FeatureManager {
 
     public Feature getFeature(final String name) {
         final FeatureDescription desc = this.activeFeatures.get(name);
-        if ( desc != null ) {
+        if (desc != null) {
             return desc.feature;
         }
         return null;
@@ -230,31 +244,33 @@ public class FeatureManager {
     }
 
     /**
-     * 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.
@@ -263,8 +279,8 @@ public class FeatureManager {
 
         @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;
         }

Added: sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java?rev=1561613&view=auto
==============================================================================
--- sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java (added)
+++ sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java Mon Jan 27 08:58:11 2014
@@ -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;
+            }
+        };
+    }
+}

Propchange: sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java?rev=1561613&r1=1561612&r2=1561613&view=diff
==============================================================================
--- sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java (original)
+++ sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java Mon Jan 27 08:58:11 2014
@@ -18,7 +18,8 @@
  */
 package org.apache.sling.featureflags.impl;
 
-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;
@@ -66,7 +67,7 @@ public class FeaturesImpl implements Fea
     }
 
     @Override
-    public ClientContext createClientContext(final SlingHttpServletRequest request) {
+    public ClientContext createClientContext(final HttpServletRequest request) {
         return this.manager.createClientContext(request);
     }
 }

Modified: sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/package-info.java
URL: http://svn.apache.org/viewvc/sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/package-info.java?rev=1561613&r1=1561612&r2=1561613&view=diff
==============================================================================
--- sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/package-info.java (original)
+++ sling/whiteboard/fmeschbe/featureflags/feature-flags/src/main/java/org/apache/sling/featureflags/package-info.java Mon Jan 27 08:58:11 2014
@@ -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;