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>.