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 <[email protected]> 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 "[email protected]" <[email protected]>.
