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/30 09:00:45 UTC

svn commit: r1562712 - in /sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags: ./ impl/

Author: fmeschbe
Date: Thu Jan 30 08:00:45 2014
New Revision: 1562712

URL: http://svn.apache.org/r1562712
Log:
SLING-3350 Simplify API and implementation:

- Remove ClientContext interface (clients directly use Features service)
- Add feature flag caching in the ExecutionContext
- Improve API doc
- Merge service implementations into FeatureManager
- Remove ResourceDecorator (see issue for reasons)
- Only one filter registration (extract ResourceResolver from request attr)

Removed:
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/ClientContext.java
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/ClientContextImpl.java
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/CurrentClientContextFilter.java
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureResourceDecorator.java
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java
Modified:
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/ExecutionContext.java
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/Feature.java
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/Features.java
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java
    sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/package-info.java

Modified: sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/ExecutionContext.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/ExecutionContext.java?rev=1562712&r1=1562711&r2=1562712&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/ExecutionContext.java (original)
+++ sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/ExecutionContext.java Thu Jan 30 08:00:45 2014
@@ -30,14 +30,14 @@ import aQute.bnd.annotation.ProviderType
  * 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
  * behavior.
+ * <p>
+ * Instances of this interface are provided by the feature manager to the
+ * {@link Feature} services. This interface is not intended to be implemented by
+ * client and application code.
  */
 @ProviderType
 public interface ExecutionContext {
@@ -47,11 +47,8 @@ public interface ExecutionContext {
      * 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>
+     * @return the request or {@code null}
      */
     HttpServletRequest getRequest();
 
@@ -61,7 +58,7 @@ public interface ExecutionContext {
      * If a {@code ResourceResolver} object is not available in the context,
      * this method may return {@code null}.
      *
-     * @return the resource resolver
+     * @return the resource resolver or {@code null}
      */
     ResourceResolver getResourceResolver();
 }

Modified: sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/Feature.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/Feature.java?rev=1562712&r1=1562711&r2=1562712&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/Feature.java (original)
+++ sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/Feature.java Thu Jan 30 08:00:45 2014
@@ -22,31 +22,18 @@ import aQute.bnd.annotation.ConsumerType
 
 /**
  * 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}.
+ * accessible through the {@link Features} service while those with lower
+ * service rankings are ignored.
+ * <p>
+ * This interface is expected to be implemented by feature providers.
  */
 @ConsumerType
 public interface Feature {
 
     /**
-     * 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
@@ -61,4 +48,24 @@ public interface Feature {
      *         {@code null} or an empty string.
      */
     String getDescription();
+
+    /**
+     * 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}.
+     * <p>
+     * This method is called by the {@link Feature} manager and is not intended
+     * to be called by application code directly.
+     *
+     * @param context The {@link ExecutionContext} providing a context to
+     *            evaluate whether the feature is enabled or not.
+     *            Implementations must not hold on to this context instance or
+     *            the values provided for longer than executing this method.
+     * @return {@code true} if this {@code Feature} is enabled in the given
+     *         {@link ExecutionContext}.
+     */
+    boolean isEnabled(ExecutionContext context);
 }

Modified: sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/Features.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/Features.java?rev=1562712&r1=1562711&r2=1562712&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/Features.java (original)
+++ sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/Features.java Thu Jan 30 08:00:45 2014
@@ -18,45 +18,34 @@
  */
 package org.apache.sling.featureflags;
 
-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 {@code Features} service is the applications access point to the Feature
+ * Flag functionality. 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 (known) feature names.
+     * Get the list of all (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 names of the known features
+     * @return The known features
      */
-    String[] getAvailableFeatureNames();
+    Feature[] getFeatures();
 
     /**
-     * Get the list of all available (known) features.
+     * Returns the feature with the given name.
      * <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.
-     *
      * @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}.
@@ -64,62 +53,17 @@ public interface Features {
     Feature getFeature(String name);
 
     /**
-     * Checks whether a feature with the given name is available (known).
+     * Returns {@code true} if a feature with the given name is known and
+     * enabled under the current {@link ExecutionContext}.
      * <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.
-     *
-     * @return A client context.
-     */
-    ClientContext getCurrentClientContext();
-
-    /**
-     * Create a client context for the resource resolver.
-     * <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.
-     * <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}
+     * @param name The name of the feature to check for enablement.
+     * @return {@code true} if the named feature is known and enabled.
+     *         Specifically {@code false} is also returned if the named feature
+     *         is not known.
      */
-    ClientContext createClientContext(HttpServletRequest request);
+    boolean isEnabled(String name);
 }

Modified: sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java?rev=1562712&r1=1562711&r2=1562712&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java (original)
+++ sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java Thu Jan 30 08:00:45 2014
@@ -18,41 +18,40 @@
  */
 package org.apache.sling.featureflags.impl;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import javax.servlet.http.HttpServletRequest;
 
-import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.auth.core.AuthenticationSupport;
 import org.apache.sling.featureflags.ExecutionContext;
+import org.apache.sling.featureflags.Feature;
 
 /**
  * Implementation of the provider context.
  */
 public class ExecutionContextImpl implements ExecutionContext {
 
+    private static final String REQUEST_ATTRIBUTE_RESOLVER = "org.apache.sling.auth.core.ResourceResolver";
+
     private final ResourceResolver resourceResolver;
 
     private final HttpServletRequest request;
 
-    public ExecutionContextImpl(final ResourceResolver resourceResolver) {
-        this.request = null;
-        this.resourceResolver = resourceResolver;
-    }
+    private final Map<String, Boolean> featureCache;
 
     public ExecutionContextImpl(final HttpServletRequest request) {
-        this.request = request;
-        ResourceResolver resolver = (request instanceof SlingHttpServletRequest)
-                ? ((SlingHttpServletRequest) request).getResourceResolver()
-                : null;
-        if ( resolver == null ) {
-            // get ResourceResolver (set by AuthenticationSupport)
-            final Object resolverObject = request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER);
-            resolver = (resolverObject instanceof ResourceResolver)
-                    ? (ResourceResolver) resolverObject
-                    : null;
-
+        ResourceResolver resourceResolver = null;
+        if (request != null) {
+            Object resolverObject = request.getAttribute(REQUEST_ATTRIBUTE_RESOLVER);
+            if (resolverObject instanceof ResourceResolver) {
+                resourceResolver = (ResourceResolver) resolverObject;
+            }
         }
-        this.resourceResolver = resolver;
+
+        this.request = request;
+        this.resourceResolver = resourceResolver;
+        this.featureCache = new HashMap<String, Boolean>();
     }
 
     @Override
@@ -64,4 +63,14 @@ public class ExecutionContextImpl implem
     public ResourceResolver getResourceResolver() {
         return this.resourceResolver;
     }
+
+    boolean isEnabled(Feature feature) {
+        final String name = feature.getName();
+        Boolean entry = this.featureCache.get(name);
+        if (entry == null) {
+            entry = Boolean.valueOf(feature.isEnabled(this));
+            this.featureCache.put(name, entry);
+        }
+        return entry;
+    }
 }

Modified: sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java?rev=1562712&r1=1562711&r2=1562712&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java (original)
+++ sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java Thu Jan 30 08:00:45 2014
@@ -18,34 +18,36 @@
  */
 package org.apache.sling.featureflags.impl;
 
+import java.io.IOException;
+import java.io.PrintWriter;
 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 javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
 import javax.servlet.Servlet;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
-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.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Properties;
+import org.apache.felix.scr.annotations.Property;
 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.resource.ResourceDecorator;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.featureflags.ClientContext;
-import org.apache.sling.featureflags.ExecutionContext;
+import org.apache.felix.scr.annotations.Service;
 import org.apache.sling.featureflags.Feature;
 import org.apache.sling.featureflags.Features;
-import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
-import org.osgi.framework.ServiceRegistration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -53,71 +55,114 @@ import org.slf4j.LoggerFactory;
  * This service implements the feature handling. It keeps track of all
  * {@link Feature} services.
  */
-@Component
+@Component(policy = ConfigurationPolicy.IGNORE)
+@Service
 @Reference(
         name = "feature",
         cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
         policy = ReferencePolicy.DYNAMIC,
         referenceInterface = Feature.class)
-public class FeatureManager {
+@Properties({
+    @Property(name = "felix.webconsole.label", value = "features"),
+    @Property(name = "felix.webconsole.title", value = "Features"),
+    @Property(name = "felix.webconsole.category", value = "Sling"),
+    @Property(name = "pattern", value = "/.*"),
+    @Property(name = "service.ranking", intValue = 0x4000)
+})
+public class FeatureManager implements Features, Filter, Servlet {
 
     private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
-    private final ThreadLocal<ClientContext> perThreadClientContext = new ThreadLocal<ClientContext>();
+    private final ThreadLocal<ExecutionContextImpl> perThreadClientContext = new ThreadLocal<ExecutionContextImpl>();
 
-    private final ClientContext defaultClientContext = new ClientContext() {
-        @Override
-        public boolean isEnabled(final String featureName) {
-            return false;
+    private final Map<String, List<FeatureDescription>> allFeatures = new HashMap<String, List<FeatureDescription>>();
+
+    private Map<String, Feature> activeFeatures = Collections.emptyMap();
+
+    private ServletConfig servletConfig;
+
+    //--- Features
+
+    public Feature[] getFeatures() {
+        final Map<String, Feature> activeFeatures = this.activeFeatures;
+        return activeFeatures.values().toArray(new Feature[activeFeatures.size()]);
+    }
+
+    public Feature getFeature(final String name) {
+        return this.activeFeatures.get(name);
+    }
+
+    public boolean isEnabled(String featureName) {
+        final Feature feature = this.getFeature(featureName);
+        if (feature != null) {
+            return getCurrentExecutionContext().isEnabled(feature);
         }
+        return false;
+    }
 
-        @Override
-        public Collection<Feature> getEnabledFeatures() {
-            return Collections.emptyList();
+    //--- Filter
+
+    @Override
+    public void init(FilterConfig filterConfig) {
+        // nothing todo do
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
+            ServletException {
+        try {
+            this.pushContext((HttpServletRequest) request);
+            chain.doFilter(request, response);
+        } finally {
+            this.popContext();
         }
-    };
+    }
 
-    private final Map<String, List<FeatureDescription>> allFeatures = new HashMap<String, List<FeatureDescription>>();
+    @Override
+    public void destroy() {
+        // method shared by Servlet and Filter interface
+        this.servletConfig = null;
+    }
 
-    private Map<String, Feature> activeFeatures = Collections.emptyMap();
+    //--- Servlet
 
-    private List<ServiceRegistration> services;
+    @Override
+    public void init(ServletConfig config) {
+        this.servletConfig = config;
+    }
 
-    @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("pattern", "/.*");
-                    put("service.ranking", Integer.MIN_VALUE);
-                }
-            }));
-        this.services = services;
+    @Override
+    public ServletConfig getServletConfig() {
+        return this.servletConfig;
     }
 
-    @Deactivate
-    private void deactivate() {
-        if (this.services != null) {
-            for (ServiceRegistration service : this.services) {
-                if (service != null) {
-                    service.unregister();
+    @Override
+    public String getServletInfo() {
+        return "Features";
+    }
+
+    @Override
+    public void service(ServletRequest req, ServletResponse res) throws IOException {
+        if ("GET".equals(((HttpServletRequest) req).getMethod())) {
+            final PrintWriter pw = res.getWriter();
+            final Feature[] features = getFeatures();
+            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 ExecutionContextImpl ctx = getCurrentExecutionContext();
+                for (final Feature feature : features) {
+                    pw.printf("<tr><td>%s</td><td>%s</td><td>%s</td></tr>%n", feature.getName(),
+                        feature.getDescription(), ctx.isEnabled(feature));
                 }
+                pw.println("</table>");
             }
-            this.services.clear();
-            this.services = null;
+        } else {
+            ((HttpServletResponse) res).sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+            res.flushBuffer();
         }
     }
 
@@ -176,79 +221,17 @@ public class FeatureManager {
 
     //--- Client Context management and access
 
-    ClientContext getCurrentClientContext() {
-        ClientContext result = perThreadClientContext.get();
-        if (result == null) {
-            result = defaultClientContext;
-        }
-        return result;
-    }
-
-    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;
-    }
-
-    void unsetCurrentClientContext(final ClientContext previous) {
-        if (previous != null) {
-            perThreadClientContext.set(previous);
-        } else {
-            perThreadClientContext.remove();
-        }
-    }
-
-    ClientContext createClientContext(final ResourceResolver resolver) {
-        if (resolver == null) {
-            throw new IllegalArgumentException("Resolver must not be null.");
-        }
-        final ExecutionContext providerContext = new ExecutionContextImpl(resolver);
-        final ClientContext ctx = this.createClientContext(providerContext);
-        return ctx;
-    }
-
-    ClientContext createClientContext(final HttpServletRequest request) {
-        if (request == null) {
-            throw new IllegalArgumentException("Request must not be null.");
-        }
-        final ExecutionContext providerContext = new ExecutionContextImpl(request);
-        final ClientContext ctx = this.createClientContext(providerContext);
-        return ctx;
+    void pushContext(final HttpServletRequest request) {
+        this.perThreadClientContext.set(new ExecutionContextImpl(request));
     }
 
-    private ClientContextImpl createClientContext(final ExecutionContext providerContext) {
-        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());
-            }
-        }
-
-        return new ClientContextImpl(providerContext, enabledFeatures);
-    }
-
-    //--- Feature access
-
-    Feature[] getAvailableFeatures() {
-        final Map<String, Feature> activeFeatures = this.activeFeatures;
-        return activeFeatures.values().toArray(new Feature[activeFeatures.size()]);
-    }
-
-    Feature getFeature(final String name) {
-        return this.activeFeatures.get(name);
-    }
-
-    String[] getAvailableFeatureNames() {
-        final Map<String, Feature> activeFeatures = this.activeFeatures;
-        return activeFeatures.keySet().toArray(new String[activeFeatures.size()]);
+    void popContext() {
+        this.perThreadClientContext.set(null);
     }
 
-    boolean isAvailable(final String featureName) {
-        return this.activeFeatures.containsKey(featureName);
+    ExecutionContextImpl getCurrentExecutionContext() {
+        ExecutionContextImpl ctx = this.perThreadClientContext.get();
+        return (ctx != null) ? ctx : new ExecutionContextImpl(null);
     }
 
     /**

Modified: sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/package-info.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/package-info.java?rev=1562712&r1=1562711&r2=1562712&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/package-info.java (original)
+++ sling/trunk/contrib/extensions/feature-flags/src/main/java/org/apache/sling/featureflags/package-info.java Thu Jan 30 08:00:45 2014
@@ -40,16 +40,18 @@
  * <table>
  *  <tr>
  *      <td>{@code name}</td>
- *      <td>Short name of this feature. This name is used to refer to this feature
+ *      <td>Short name of the feature. This name is used to refer to the 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>
+ *          identity. It is strongly recommended to define a useful and unique name
+ *          for the feature</td>
  *  </tr>
  *  <tr>
  *      <td>{@code description}</td>
  *      <td>Description for the feature. The intent is to describe the behavior
  *          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>
+ *          to define this property. The default value is the name of the feature
+ *          as derived from the {@code name} property.</td>
  *  </tr>
  *  <tr>
  *      <td>{@code enabled}</td>