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/10/18 23:19:49 UTC

[sling-org-apache-sling-featureflags] 01/28: SLING-3353 Move feature flags from contrib to bundles

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-featureflags.git

commit 161c7370ab9a72ca242e9075c525675d6b7d9e18
Author: Felix Meschberger <fm...@apache.org>
AuthorDate: Thu Jan 30 09:48:41 2014 +0000

    SLING-3353 Move feature flags from contrib to bundles
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1562757 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  70 +++++
 .../sling/featureflags/ExecutionContext.java       |  64 +++++
 .../org/apache/sling/featureflags/Feature.java     |  71 +++++
 .../org/apache/sling/featureflags/Features.java    |  69 +++++
 .../sling/featureflags/impl/ConfiguredFeature.java | 105 ++++++++
 .../featureflags/impl/ExecutionContextImpl.java    |  76 ++++++
 .../sling/featureflags/impl/FeatureManager.java    | 287 +++++++++++++++++++++
 .../apache/sling/featureflags/package-info.java    |  70 +++++
 8 files changed, 812 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..6ea7c84
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>18</version>
+    </parent>
+    
+    <groupId>org.apache.sling</groupId>
+    <artifactId>org.apache.sling.featureflags</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+    <name>Apache Sling Feature Flags</name>
+    
+    <properties>
+        <sling.java.version>6</sling.java.version>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+        </plugins>
+    </build>
+    
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.commons.osgi</artifactId>
+            <version>2.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.auth.core</artifactId>
+            <version>1.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/featureflags/ExecutionContext.java b/src/main/java/org/apache/sling/featureflags/ExecutionContext.java
new file mode 100644
index 0000000..321a092
--- /dev/null
+++ b/src/main/java/org/apache/sling/featureflags/ExecutionContext.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.featureflags;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.resource.ResourceResolver;
+
+import aQute.bnd.annotation.ProviderType;
+
+/**
+ * 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>
+ * 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 {
+
+    /**
+     * 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}.
+     *
+     * @return the request or {@code null}
+     */
+    HttpServletRequest getRequest();
+
+    /**
+     * 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 or {@code null}
+     */
+    ResourceResolver getResourceResolver();
+}
diff --git a/src/main/java/org/apache/sling/featureflags/Feature.java b/src/main/java/org/apache/sling/featureflags/Feature.java
new file mode 100644
index 0000000..1d835b9
--- /dev/null
+++ b/src/main/java/org/apache/sling/featureflags/Feature.java
@@ -0,0 +1,71 @@
+/*
+ * 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 aQute.bnd.annotation.ConsumerType;
+
+/**
+ * A feature is defined by its name. Features are registered as OSGi services.
+ * <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 while those with lower
+ * service rankings are ignored.
+ * <p>
+ * This interface is expected to be implemented by feature providers.
+ */
+@ConsumerType
+public interface Feature {
+
+    /**
+     * 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();
+
+    /**
+     * 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);
+}
diff --git a/src/main/java/org/apache/sling/featureflags/Features.java b/src/main/java/org/apache/sling/featureflags/Features.java
new file mode 100644
index 0000000..a1d2a69
--- /dev/null
+++ b/src/main/java/org/apache/sling/featureflags/Features.java
@@ -0,0 +1,69 @@
+/*
+ * 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 aQute.bnd.annotation.ProviderType;
+
+/**
+ * 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 (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[] getFeatures();
+
+    /**
+     * 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}.
+     *
+     * @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);
+
+    /**
+     * 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 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.
+     */
+    boolean isEnabled(String name);
+}
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..d79d8cf
--- /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 = "Apache Sling 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/ExecutionContextImpl.java b/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java
new file mode 100644
index 0000000..76d40c7
--- /dev/null
+++ b/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java
@@ -0,0 +1,76 @@
+/*
+ * 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.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.resource.ResourceResolver;
+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;
+
+    private final Map<String, Boolean> featureCache;
+
+    public ExecutionContextImpl(final HttpServletRequest request) {
+        ResourceResolver resourceResolver = null;
+        if (request != null) {
+            Object resolverObject = request.getAttribute(REQUEST_ATTRIBUTE_RESOLVER);
+            if (resolverObject instanceof ResourceResolver) {
+                resourceResolver = (ResourceResolver) resolverObject;
+            }
+        }
+
+        this.request = request;
+        this.resourceResolver = resourceResolver;
+        this.featureCache = new HashMap<String, Boolean>();
+    }
+
+    @Override
+    public HttpServletRequest getRequest() {
+        return this.request;
+    }
+
+    @Override
+    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;
+    }
+}
diff --git a/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java b/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java
new file mode 100644
index 0000000..c4f6b84
--- /dev/null
+++ b/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java
@@ -0,0 +1,287 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+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.Component;
+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.felix.scr.annotations.Service;
+import org.apache.sling.featureflags.Feature;
+import org.apache.sling.featureflags.Features;
+import org.osgi.framework.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This service implements the feature handling. It keeps track of all
+ * {@link Feature} services.
+ */
+@Component(policy = ConfigurationPolicy.IGNORE)
+@Service
+@Reference(
+        name = "feature",
+        cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
+        policy = ReferencePolicy.DYNAMIC,
+        referenceInterface = Feature.class)
+@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<ExecutionContextImpl> perThreadClientContext = new ThreadLocal<ExecutionContextImpl>();
+
+    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;
+    }
+
+    //--- 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();
+        }
+    }
+
+    @Override
+    public void destroy() {
+        // method shared by Servlet and Filter interface
+        this.servletConfig = null;
+    }
+
+    //--- Servlet
+
+    @Override
+    public void init(ServletConfig config) {
+        this.servletConfig = config;
+    }
+
+    @Override
+    public ServletConfig getServletConfig() {
+        return this.servletConfig;
+    }
+
+    @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>");
+            }
+        } else {
+            ((HttpServletResponse) res).sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+            res.flushBuffer();
+        }
+    }
+
+    //--- 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) {
+                candidates = new ArrayList<FeatureDescription>();
+                this.allFeatures.put(name, candidates);
+            }
+            candidates.add(info);
+            Collections.sort(candidates);
+
+            this.calculateActiveProviders();
+        }
+    }
+
+    // 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
+                candidates.remove(info);
+                if (candidates.size() == 0) {
+                    this.allFeatures.remove(name);
+                }
+            }
+            this.calculateActiveProviders();
+        }
+    }
+
+    // calculates map of active features (eliminating Feature name
+    // collisions). Must be called while synchronized on this.allFeatures
+    private void calculateActiveProviders() {
+        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.feature);
+            if (entry.getValue().size() > 1) {
+                logger.warn("More than one feature service for feature {}", entry.getKey());
+            }
+        }
+        this.activeFeatures = activeMap;
+    }
+
+    //--- Client Context management and access
+
+    void pushContext(final HttpServletRequest request) {
+        this.perThreadClientContext.set(new ExecutionContextImpl(request));
+    }
+
+    void popContext() {
+        this.perThreadClientContext.set(null);
+    }
+
+    ExecutionContextImpl getCurrentExecutionContext() {
+        ExecutionContextImpl ctx = this.perThreadClientContext.get();
+        return (ctx != null) ? ctx : new ExecutionContextImpl(null);
+    }
+
+    /**
+     * 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) {
+            this.feature = feature;
+            final Object sr = props.get(Constants.SERVICE_RANKING);
+            if (sr instanceof Integer) {
+                this.ranking = (Integer) sr;
+            } else {
+                this.ranking = 0;
+            }
+            this.serviceId = (Long) props.get(Constants.SERVICE_ID);
+        }
+
+        @Override
+        public int compareTo(final FeatureDescription o) {
+            if (this.ranking < o.ranking) {
+                return 1;
+            } else if (this.ranking > o.ranking) {
+                return -1;
+            }
+            // If ranks are equal, then sort by service id in descending order.
+            return (this.serviceId < o.serviceId) ? -1 : 1;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (obj instanceof FeatureDescription) {
+                return ((FeatureDescription) obj).serviceId == this.serviceId;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (int) (serviceId ^ (serviceId >>> 32));
+            return result;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/featureflags/package-info.java b/src/main/java/org/apache/sling/featureflags/package-info.java
new file mode 100644
index 0000000..c1803b5
--- /dev/null
+++ b/src/main/java/org/apache/sling/featureflags/package-info.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+/**
+ * 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 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 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 name of the feature
+ *          as derived from the {@code 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;
+
+import aQute.bnd.annotation.Version;
+

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