This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.featureflags-1.0.0 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-featureflags.git
commit 7d315bae21cfaf139688360fbb1a400c964c0274 Author: Felix Meschberger <[email protected]> AuthorDate: Thu Jan 30 08:00:45 2014 +0000 SLING-3350 Simplify API and implementation: - Remove ClientContext interface (clients directly use Features service) - Add feature flag caching in the ExecutionContext - Improve API doc - Merge service implementations into FeatureManager - Remove ResourceDecorator (see issue for reasons) - Only one filter registration (extract ResourceResolver from request attr) git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/contrib/extensions/feature-flags@1562712 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/sling/featureflags/ClientContext.java | 53 ----- .../sling/featureflags/ExecutionContext.java | 15 +- .../org/apache/sling/featureflags/Feature.java | 41 ++-- .../org/apache/sling/featureflags/Features.java | 84 ++------ .../sling/featureflags/impl/ClientContextImpl.java | 74 ------- .../impl/CurrentClientContextFilter.java | 64 ------ .../featureflags/impl/ExecutionContextImpl.java | 45 ++-- .../sling/featureflags/impl/FeatureManager.java | 233 ++++++++++----------- .../impl/FeatureResourceDecorator.java | 57 ----- .../featureflags/impl/FeatureWebConsolePlugin.java | 74 ------- .../sling/featureflags/impl/FeaturesImpl.java | 73 ------- .../apache/sling/featureflags/package-info.java | 8 +- 12 files changed, 184 insertions(+), 637 deletions(-) diff --git a/src/main/java/org/apache/sling/featureflags/ClientContext.java b/src/main/java/org/apache/sling/featureflags/ClientContext.java deleted file mode 100644 index 33a392c..0000000 --- a/src/main/java/org/apache/sling/featureflags/ClientContext.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 java.util.Collection; - -import aQute.bnd.annotation.ProviderType; - -/** - * The client context can be used by client code to check whether a specific - * feature is enable. - * <p> - * Prepared {@code ClientContext} instances are available through the - * {@link Features} service. Consumers of this interface are not expected to - * implement it. - */ -@ProviderType -public interface ClientContext { - - /** - * Returns {@code true} if the named feature is enabled. - * - * @param featureName The name of the feature. - * @return {@code true} if the named feature is enabled. {@code false} is - * returned if the named feature is not enabled, is not known or the - * {@code featureName} parameter is {@code null} or an empty String. - */ - boolean isEnabled(String featureName); - - /** - * Returns a possibly empty collection of enabled {@link Feature} instances. - * - * @return The collection of enabled {@link Feature} instances. This - * collection may be empty and is not modifiable. - */ - Collection<Feature> getEnabledFeatures(); -} diff --git a/src/main/java/org/apache/sling/featureflags/ExecutionContext.java b/src/main/java/org/apache/sling/featureflags/ExecutionContext.java index 1e91088..321a092 100644 --- a/src/main/java/org/apache/sling/featureflags/ExecutionContext.java +++ b/src/main/java/org/apache/sling/featureflags/ExecutionContext.java @@ -30,14 +30,14 @@ import aQute.bnd.annotation.ProviderType; * provided to the {@link Feature#isEnabled(ExecutionContext)} to help * evaluating whether the feature is enabled or not. * <p> - * The {@link Features} service {@link ClientContext} generating methods create - * an instance of this to collect the enabled {@link Feature} services for the - * creation of the {@link ClientContext} instance. - * <p> * This object provides access to live data and must only be used to read * information. Modifying content through a {@code ResourceResolver} directly or * indirectly provided by this object is considered inappropriate and faulty * behavior. + * <p> + * Instances of this interface are provided by the feature manager to the + * {@link Feature} services. This interface is not intended to be implemented by + * client and application code. */ @ProviderType public interface ExecutionContext { @@ -47,11 +47,8 @@ public interface ExecutionContext { * may influence the decision whether a {@link Feature} is enabled or not. * If a {@code HttpServletRequest} object is not available in the context, * this method may return {@code null}. - * <p> - * In a Sling request processing context, the {@code HttpServletRequest} - * object returned may actually be a {@code SlingHttpServletRequest}. * - * @return the request or <code>null</code> + * @return the request or {@code null} */ HttpServletRequest getRequest(); @@ -61,7 +58,7 @@ public interface ExecutionContext { * If a {@code ResourceResolver} object is not available in the context, * this method may return {@code null}. * - * @return the resource resolver + * @return the resource resolver or {@code null} */ ResourceResolver getResourceResolver(); } diff --git a/src/main/java/org/apache/sling/featureflags/Feature.java b/src/main/java/org/apache/sling/featureflags/Feature.java index 3984df5..1d835b9 100644 --- a/src/main/java/org/apache/sling/featureflags/Feature.java +++ b/src/main/java/org/apache/sling/featureflags/Feature.java @@ -22,31 +22,18 @@ import aQute.bnd.annotation.ConsumerType; /** * A feature is defined by its name. Features are registered as OSGi services. - * This interface is expected to be implemented by feature providers. * <p> * Feature {@link #getName() names} should be globally unique. If multiple * features have the same name, the feature with the highest service ranking is - * accessible through the {@link Features} service and the {@link ClientContext}. + * accessible through the {@link Features} service while those with lower + * service rankings are ignored. + * <p> + * This interface is expected to be implemented by feature providers. */ @ConsumerType public interface Feature { /** - * Checks whether the feature is enabled for the given execution context. - * <p> - * Multiple calls to this method may but are not required to return the same - * value. For example the return value may depend on the time of day, some - * random number or some information provided by the given - * {@link ExecutionContext}. - * - * @param context The {@link ExecutionContext} providing a context to - * evaluate whether the feature is enabled or not. - * @return {@code true} if this {@code Feature} is enabled in the given - * {@link ExecutionContext}. - */ - boolean isEnabled(ExecutionContext context); - - /** * The name of the feature. * * @return The name of this feature which must not be {@code null} or an @@ -61,4 +48,24 @@ public interface Feature { * {@code null} or an empty string. */ String getDescription(); + + /** + * Checks whether the feature is enabled for the given execution context. + * <p> + * Multiple calls to this method may but are not required to return the same + * value. For example the return value may depend on the time of day, some + * random number or some information provided by the given + * {@link ExecutionContext}. + * <p> + * This method is called by the {@link Feature} manager and is not intended + * to be called by application code directly. + * + * @param context The {@link ExecutionContext} providing a context to + * evaluate whether the feature is enabled or not. + * Implementations must not hold on to this context instance or + * the values provided for longer than executing this method. + * @return {@code true} if this {@code Feature} is enabled in the given + * {@link ExecutionContext}. + */ + boolean isEnabled(ExecutionContext context); } diff --git a/src/main/java/org/apache/sling/featureflags/Features.java b/src/main/java/org/apache/sling/featureflags/Features.java index eb5faac..a1d2a69 100644 --- a/src/main/java/org/apache/sling/featureflags/Features.java +++ b/src/main/java/org/apache/sling/featureflags/Features.java @@ -18,45 +18,34 @@ */ package org.apache.sling.featureflags; -import javax.servlet.http.HttpServletRequest; - -import org.apache.sling.api.resource.ResourceResolver; - import aQute.bnd.annotation.ProviderType; /** - * The features service is the central gateway for feature handling. It can be - * used to query the available features and to create client contexts to be used - * for enabled feature checking. + * The {@code Features} service is the applications access point to the Feature + * Flag functionality. It can be used to query the available features and to + * create client contexts to be used for enabled feature checking. */ @ProviderType public interface Features { /** - * Get the list of all available (known) feature names. + * Get the list of all (known) features. * <p> * Features are known if they are registered as {@link Feature} services or * are configured with OSGi configuration whose factory PID is * {@code org.apache.sling.featureflags.Feature}. * - * @return The names of the known features + * @return The known features */ - String[] getAvailableFeatureNames(); + Feature[] getFeatures(); /** - * Get the list of all available (known) features. + * Returns the feature with the given name. * <p> * Features are known if they are registered as {@link Feature} services or * are configured with OSGi configuration whose factory PID is * {@code org.apache.sling.featureflags.Feature}. * - * @return The known features - */ - Feature[] getAvailableFeatures(); - - /** - * Returns the feature with the given name. - * * @param name The name of the feature. * @return The feature or <code>null</code> if not known or the name is an * empty string or {@code null}. @@ -64,62 +53,17 @@ public interface Features { Feature getFeature(String name); /** - * Checks whether a feature with the given name is available (known). + * Returns {@code true} if a feature with the given name is known and + * enabled under the current {@link ExecutionContext}. * <p> * Features are known if they are registered as {@link Feature} services or * are configured with OSGi configuration whose factory PID is * {@code org.apache.sling.featureflags.Feature}. * - * @param featureName The name of the feature to check for availability. - * @return {@code true} if the named feature is available. - */ - boolean isAvailable(String featureName); - - /** - * Returns the current client context. This method always returns a client - * context object. - * - * @return A client context. - */ - ClientContext getCurrentClientContext(); - - /** - * Create a client context for the resource resolver. - * <p> - * The {@link ClientContext} is a snapshot of the enablement state of the - * features at the time of creation. A change in the feature enablement - * state is not reflected in {@link ClientContext} objects created prior to - * changing the state. - * <p> - * The {@link ClientContext} returned is not available through the - * {@link #getCurrentClientContext()} method. - * - * @param resolver The {@code ResourceResolver} to base the - * {@link ClientContext} on. - * @return A newly created client context based on the given - * {@code ResourceResolver}. - * @throws IllegalArgumentException If {@code resolver} is {@code null} - */ - ClientContext createClientContext(ResourceResolver resolver); - - /** - * Create a client context for the request. - * <p> - * The {@link ClientContext} is a snapshot of the enablement state of the - * features at the time of creation. A change in the feature enablement - * state is not reflected in {@link ClientContext} objects created prior to - * changing the state. - * <p> - * The {@link ClientContext} returned is not available through the - * {@link #getCurrentClientContext()} method. - * - * @param request The {@code HttpServletRequest} to base the - * {@link ClientContext} on. If this is a - * {@code SlingHttpServletContext} the {@link ClientContext}'s - * resource resolver is set to the request's resource resolver. - * @return A newly created client context based on the given - * {@code HttpServletRequest}. - * @throws IllegalArgumentException If {@code request} is {@code null} + * @param name The name of the feature to check for enablement. + * @return {@code true} if the named feature is known and enabled. + * Specifically {@code false} is also returned if the named feature + * is not known. */ - ClientContext createClientContext(HttpServletRequest request); + boolean isEnabled(String name); } diff --git a/src/main/java/org/apache/sling/featureflags/impl/ClientContextImpl.java b/src/main/java/org/apache/sling/featureflags/impl/ClientContextImpl.java deleted file mode 100644 index dc02b25..0000000 --- a/src/main/java/org/apache/sling/featureflags/impl/ClientContextImpl.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.apache.sling.api.resource.ResourceDecorator; -import org.apache.sling.featureflags.ClientContext; -import org.apache.sling.featureflags.Feature; -import org.apache.sling.featureflags.ExecutionContext; - -/** - * Implementation of the client context - */ -public class ClientContextImpl implements ClientContext { - - private final ExecutionContext featureContext; - - private final Map<String, Feature> enabledFeatures; - - private final List<ResourceDecorator> resourceDecorators; - - public ClientContextImpl(final ExecutionContext featureContext, final Map<String, Feature> features) { - ArrayList<ResourceDecorator> resourceDecorators = new ArrayList<ResourceDecorator>(features.size()); - for (final Feature f : features.values()) { - if (f instanceof ResourceDecorator) { - resourceDecorators.add((ResourceDecorator) f); - } - } - resourceDecorators.trimToSize(); - - this.featureContext = featureContext; - this.enabledFeatures = Collections.unmodifiableMap(features); - this.resourceDecorators = Collections.unmodifiableList(resourceDecorators); - } - - public ExecutionContext getFeatureContext() { - return this.featureContext; - } - - @Override - public boolean isEnabled(final String featureName) { - return this.enabledFeatures.get(featureName) != null; - } - - @Override - public Collection<Feature> getEnabledFeatures() { - return this.enabledFeatures.values(); - } - - public List<ResourceDecorator> getResourceDecorators() { - return this.resourceDecorators; - } -} diff --git a/src/main/java/org/apache/sling/featureflags/impl/CurrentClientContextFilter.java b/src/main/java/org/apache/sling/featureflags/impl/CurrentClientContextFilter.java deleted file mode 100644 index da6287f..0000000 --- a/src/main/java/org/apache/sling/featureflags/impl/CurrentClientContextFilter.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import org.apache.sling.featureflags.ClientContext; - -/** - * This general servlet filter sets the current client context to the current - * request. - */ -public class CurrentClientContextFilter implements Filter { - - private final FeatureManager featureManager; - - public CurrentClientContextFilter(final FeatureManager featureManager) { - this.featureManager = featureManager; - } - - @Override - public void init(final FilterConfig config) { - // nothing to do - } - - @Override - public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) - throws IOException, ServletException { - - ClientContext current = this.featureManager.setCurrentClientContext(req); - try { - chain.doFilter(req, res); - } finally { - this.featureManager.unsetCurrentClientContext(current); - } - } - - @Override - public void destroy() { - // nothing to do - } -} diff --git a/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java b/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java index 82f82da..76d40c7 100644 --- a/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java +++ b/src/main/java/org/apache/sling/featureflags/impl/ExecutionContextImpl.java @@ -18,41 +18,40 @@ */ package org.apache.sling.featureflags.impl; +import java.util.HashMap; +import java.util.Map; + import javax.servlet.http.HttpServletRequest; -import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.ResourceResolver; -import org.apache.sling.auth.core.AuthenticationSupport; import org.apache.sling.featureflags.ExecutionContext; +import org.apache.sling.featureflags.Feature; /** * Implementation of the provider context. */ public class ExecutionContextImpl implements ExecutionContext { + private static final String REQUEST_ATTRIBUTE_RESOLVER = "org.apache.sling.auth.core.ResourceResolver"; + private final ResourceResolver resourceResolver; private final HttpServletRequest request; - public ExecutionContextImpl(final ResourceResolver resourceResolver) { - this.request = null; - this.resourceResolver = resourceResolver; - } + private final Map<String, Boolean> featureCache; public ExecutionContextImpl(final HttpServletRequest request) { - this.request = request; - ResourceResolver resolver = (request instanceof SlingHttpServletRequest) - ? ((SlingHttpServletRequest) request).getResourceResolver() - : null; - if ( resolver == null ) { - // get ResourceResolver (set by AuthenticationSupport) - final Object resolverObject = request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER); - resolver = (resolverObject instanceof ResourceResolver) - ? (ResourceResolver) resolverObject - : null; - + ResourceResolver resourceResolver = null; + if (request != null) { + Object resolverObject = request.getAttribute(REQUEST_ATTRIBUTE_RESOLVER); + if (resolverObject instanceof ResourceResolver) { + resourceResolver = (ResourceResolver) resolverObject; + } } - this.resourceResolver = resolver; + + this.request = request; + this.resourceResolver = resourceResolver; + this.featureCache = new HashMap<String, Boolean>(); } @Override @@ -64,4 +63,14 @@ public class ExecutionContextImpl implements ExecutionContext { 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 index b5297a8..c4f6b84 100644 --- a/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java +++ b/src/main/java/org/apache/sling/featureflags/impl/FeatureManager.java @@ -18,34 +18,36 @@ */ package org.apache.sling.featureflags.impl; +import java.io.IOException; +import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.Hashtable; import java.util.List; import java.util.Map; import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; -import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; -import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.ConfigurationPolicy; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.ReferencePolicy; -import org.apache.sling.api.resource.ResourceDecorator; -import org.apache.sling.api.resource.ResourceResolver; -import org.apache.sling.featureflags.ClientContext; -import org.apache.sling.featureflags.ExecutionContext; +import org.apache.felix.scr.annotations.Service; import org.apache.sling.featureflags.Feature; import org.apache.sling.featureflags.Features; -import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; -import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,71 +55,114 @@ import org.slf4j.LoggerFactory; * This service implements the feature handling. It keeps track of all * {@link Feature} services. */ -@Component +@Component(policy = ConfigurationPolicy.IGNORE) +@Service @Reference( name = "feature", cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC, referenceInterface = Feature.class) -public class FeatureManager { +@Properties({ + @Property(name = "felix.webconsole.label", value = "features"), + @Property(name = "felix.webconsole.title", value = "Features"), + @Property(name = "felix.webconsole.category", value = "Sling"), + @Property(name = "pattern", value = "/.*"), + @Property(name = "service.ranking", intValue = 0x4000) +}) +public class FeatureManager implements Features, Filter, Servlet { private final Logger logger = LoggerFactory.getLogger(this.getClass()); - private final ThreadLocal<ClientContext> perThreadClientContext = new ThreadLocal<ClientContext>(); + private final ThreadLocal<ExecutionContextImpl> perThreadClientContext = new ThreadLocal<ExecutionContextImpl>(); - private final ClientContext defaultClientContext = new ClientContext() { - @Override - public boolean isEnabled(final String featureName) { - return false; + private final Map<String, List<FeatureDescription>> allFeatures = new HashMap<String, List<FeatureDescription>>(); + + private Map<String, Feature> activeFeatures = Collections.emptyMap(); + + private ServletConfig servletConfig; + + //--- Features + + public Feature[] getFeatures() { + final Map<String, Feature> activeFeatures = this.activeFeatures; + return activeFeatures.values().toArray(new Feature[activeFeatures.size()]); + } + + public Feature getFeature(final String name) { + return this.activeFeatures.get(name); + } + + public boolean isEnabled(String featureName) { + final Feature feature = this.getFeature(featureName); + if (feature != null) { + return getCurrentExecutionContext().isEnabled(feature); } + return false; + } - @Override - public Collection<Feature> getEnabledFeatures() { - return Collections.emptyList(); + //--- Filter + + @Override + public void init(FilterConfig filterConfig) { + // nothing todo do + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, + ServletException { + try { + this.pushContext((HttpServletRequest) request); + chain.doFilter(request, response); + } finally { + this.popContext(); } - }; + } - private final Map<String, List<FeatureDescription>> allFeatures = new HashMap<String, List<FeatureDescription>>(); + @Override + public void destroy() { + // method shared by Servlet and Filter interface + this.servletConfig = null; + } - private Map<String, Feature> activeFeatures = Collections.emptyMap(); + //--- Servlet - private List<ServiceRegistration> services; - - @SuppressWarnings("serial") - @Activate - private void activate(BundleContext bundleContext) { - ArrayList<ServiceRegistration> services = new ArrayList<ServiceRegistration>(); - services.add(bundleContext.registerService(Features.class.getName(), new FeaturesImpl(this), null)); - services.add(bundleContext.registerService(ResourceDecorator.class.getName(), - new FeatureResourceDecorator(this), null)); - services.add(bundleContext.registerService(Servlet.class.getName(), new FeatureWebConsolePlugin(this), - new Hashtable<String, Object>() { - { - put("felix.webconsole.label", "features"); - put("felix.webconsole.title", "Features"); - put("felix.webconsole.category", "Sling"); - } - })); - services.add(bundleContext.registerService(Filter.class.getName(), new CurrentClientContextFilter(this), - new Hashtable<String, Object>() { - { - put("pattern", "/.*"); - put("service.ranking", Integer.MIN_VALUE); - } - })); - this.services = services; + @Override + public void init(ServletConfig config) { + this.servletConfig = config; + } + + @Override + public ServletConfig getServletConfig() { + return this.servletConfig; + } + + @Override + public String getServletInfo() { + return "Features"; } - @Deactivate - private void deactivate() { - if (this.services != null) { - for (ServiceRegistration service : this.services) { - if (service != null) { - service.unregister(); + @Override + public void service(ServletRequest req, ServletResponse res) throws IOException { + if ("GET".equals(((HttpServletRequest) req).getMethod())) { + final PrintWriter pw = res.getWriter(); + final Feature[] features = getFeatures(); + if (features == null || features.length == 0) { + pw.println("<p class='statline ui-state-highlight'>No Features currently defined</p>"); + } else { + pw.printf("<p class='statline ui-state-highlight'>%d Feature(s) currently defined</p>%n", + features.length); + pw.println("<table class='nicetable'>"); + pw.println("<tr><th>Name</th><th>Description</th><th>Enabled</th></tr>"); + final ExecutionContextImpl ctx = getCurrentExecutionContext(); + for (final Feature feature : features) { + pw.printf("<tr><td>%s</td><td>%s</td><td>%s</td></tr>%n", feature.getName(), + feature.getDescription(), ctx.isEnabled(feature)); } + pw.println("</table>"); } - this.services.clear(); - this.services = null; + } else { + ((HttpServletResponse) res).sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + res.flushBuffer(); } } @@ -176,79 +221,17 @@ public class FeatureManager { //--- Client Context management and access - ClientContext getCurrentClientContext() { - ClientContext result = perThreadClientContext.get(); - if (result == null) { - result = defaultClientContext; - } - return result; - } - - ClientContext setCurrentClientContext(final ServletRequest request) { - final ClientContext current = perThreadClientContext.get(); - if (request instanceof HttpServletRequest) { - final ExecutionContext providerContext = new ExecutionContextImpl((HttpServletRequest) request); - final ClientContextImpl ctx = this.createClientContext(providerContext); - perThreadClientContext.set(ctx); - } - return current; - } - - void unsetCurrentClientContext(final ClientContext previous) { - if (previous != null) { - perThreadClientContext.set(previous); - } else { - perThreadClientContext.remove(); - } - } - - ClientContext createClientContext(final ResourceResolver resolver) { - if (resolver == null) { - throw new IllegalArgumentException("Resolver must not be null."); - } - final ExecutionContext providerContext = new ExecutionContextImpl(resolver); - final ClientContext ctx = this.createClientContext(providerContext); - return ctx; - } - - ClientContext createClientContext(final HttpServletRequest request) { - if (request == null) { - throw new IllegalArgumentException("Request must not be null."); - } - final ExecutionContext providerContext = new ExecutionContextImpl(request); - final ClientContext ctx = this.createClientContext(providerContext); - return ctx; - } - - private ClientContextImpl createClientContext(final ExecutionContext providerContext) { - final Map<String, Feature> enabledFeatures = new HashMap<String, Feature>(); - for (final Map.Entry<String, Feature> entry : this.activeFeatures.entrySet()) { - if (entry.getValue().isEnabled(providerContext)) { - enabledFeatures.put(entry.getKey(), entry.getValue()); - } - } - - return new ClientContextImpl(providerContext, enabledFeatures); + void pushContext(final HttpServletRequest request) { + this.perThreadClientContext.set(new ExecutionContextImpl(request)); } - //--- Feature access - - Feature[] getAvailableFeatures() { - final Map<String, Feature> activeFeatures = this.activeFeatures; - return activeFeatures.values().toArray(new Feature[activeFeatures.size()]); - } - - Feature getFeature(final String name) { - return this.activeFeatures.get(name); - } - - String[] getAvailableFeatureNames() { - final Map<String, Feature> activeFeatures = this.activeFeatures; - return activeFeatures.keySet().toArray(new String[activeFeatures.size()]); + void popContext() { + this.perThreadClientContext.set(null); } - boolean isAvailable(final String featureName) { - return this.activeFeatures.containsKey(featureName); + ExecutionContextImpl getCurrentExecutionContext() { + ExecutionContextImpl ctx = this.perThreadClientContext.get(); + return (ctx != null) ? ctx : new ExecutionContextImpl(null); } /** diff --git a/src/main/java/org/apache/sling/featureflags/impl/FeatureResourceDecorator.java b/src/main/java/org/apache/sling/featureflags/impl/FeatureResourceDecorator.java deleted file mode 100644 index 4985ab0..0000000 --- a/src/main/java/org/apache/sling/featureflags/impl/FeatureResourceDecorator.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 javax.servlet.http.HttpServletRequest; - -import org.apache.sling.api.resource.Resource; -import org.apache.sling.api.resource.ResourceDecorator; -import org.apache.sling.featureflags.ClientContext; - -/** - * Resource decorator implementing the resource type mapping - */ -public class FeatureResourceDecorator implements ResourceDecorator { - - private final FeatureManager manager; - - FeatureResourceDecorator(final FeatureManager manager) { - this.manager = manager; - } - - @Override - public Resource decorate(final Resource resource) { - Resource result = resource; - final ClientContext info = manager.getCurrentClientContext(); - if (info instanceof ClientContextImpl) { - for (ResourceDecorator rd : ((ClientContextImpl) info).getResourceDecorators()) { - Resource r = rd.decorate(resource); - if (r != null) { - result = r; - } - } - } - return result; - } - - @Override - public Resource decorate(final Resource resource, final HttpServletRequest request) { - return this.decorate(resource); - } -} diff --git a/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java b/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java deleted file mode 100644 index ff2b8d7..0000000 --- a/src/main/java/org/apache/sling/featureflags/impl/FeatureWebConsolePlugin.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.sling.featureflags.impl; - -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.sling.api.resource.ResourceResolver; -import org.apache.sling.featureflags.ExecutionContext; -import org.apache.sling.featureflags.Feature; - -@SuppressWarnings("serial") -public class FeatureWebConsolePlugin extends HttpServlet { - - private final FeatureManager featureManager; - - FeatureWebConsolePlugin(final FeatureManager featureManager) { - this.featureManager = featureManager; - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { - final PrintWriter pw = resp.getWriter(); - final Feature[] features = this.featureManager.getAvailableFeatures(); - if (features == null || features.length == 0) { - pw.println("<p class='statline ui-state-highlight'>No Features currently defined</p>"); - } else { - pw.printf("<p class='statline ui-state-highlight'>%d Feature(s) currently defined</p>%n", features.length); - pw.println("<table class='nicetable'>"); - pw.println("<tr><th>Name</th><th>Description</th><th>Enabled</th></tr>"); - final ExecutionContext ctx = createContext(req); - for (final Feature feature : features) { - pw.printf("<tr><td>%s</td><td>%s</td><td>%s</td></tr>%n", feature.getName(), feature.getDescription(), - feature.isEnabled(ctx)); - } - pw.println("</table>"); - } - } - - private ExecutionContext createContext(final HttpServletRequest req) { - return new ExecutionContext() { - - @Override - public ResourceResolver getResourceResolver() { - return null; - } - - @Override - public HttpServletRequest getRequest() { - return req; - } - }; - } -} diff --git a/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java b/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java deleted file mode 100644 index eb67dd6..0000000 --- a/src/main/java/org/apache/sling/featureflags/impl/FeaturesImpl.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 javax.servlet.http.HttpServletRequest; - -import org.apache.sling.api.resource.ResourceResolver; -import org.apache.sling.featureflags.ClientContext; -import org.apache.sling.featureflags.Feature; -import org.apache.sling.featureflags.Features; - -/** - * This is a wrapper around the internal feature manager. - */ -public class FeaturesImpl implements Features { - - private final FeatureManager manager; - - FeaturesImpl(final FeatureManager manager) { - this.manager = manager; - } - - @Override - public String[] getAvailableFeatureNames() { - return this.manager.getAvailableFeatureNames(); - } - - @Override - public boolean isAvailable(final String featureName) { - return this.manager.isAvailable(featureName); - } - - @Override - public Feature[] getAvailableFeatures() { - return this.manager.getAvailableFeatures(); - } - - @Override - public Feature getFeature(final String name) { - return this.manager.getFeature(name); - } - - @Override - public ClientContext getCurrentClientContext() { - return this.manager.getCurrentClientContext(); - } - - @Override - public ClientContext createClientContext(final ResourceResolver resolver) { - return this.manager.createClientContext(resolver); - } - - @Override - public ClientContext createClientContext(final HttpServletRequest request) { - return this.manager.createClientContext(request); - } -} diff --git a/src/main/java/org/apache/sling/featureflags/package-info.java b/src/main/java/org/apache/sling/featureflags/package-info.java index 41b243d..c1803b5 100644 --- a/src/main/java/org/apache/sling/featureflags/package-info.java +++ b/src/main/java/org/apache/sling/featureflags/package-info.java @@ -40,16 +40,18 @@ * <table> * <tr> * <td>{@code name}</td> - * <td>Short name of this feature. This name is used to refer to this feature + * <td>Short name of the feature. This name is used to refer to the feature * when checking for it to be enabled or not. This property is required * and defaults to a name derived from the feature's class name and object - * identity. It is strongly recommended to define a useful and unique for the feature</td> + * identity. It is strongly recommended to define a useful and unique name + * for the feature</td> * </tr> * <tr> * <td>{@code description}</td> * <td>Description for the feature. The intent is to describe the behavior * of the application if this feature would be enabled. It is recommended - * to define this property. The default value is the value of the name property.</td> + * to define this property. The default value is the name of the feature + * as derived from the {@code name} property.</td> * </tr> * <tr> * <td>{@code enabled}</td> -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
