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 b660cb40e1ad5fea7b3b8cbd2683b673f2b45815 Author: Carsten Ziegeler <[email protected]> AuthorDate: Thu Dec 19 01:21:55 2013 +0000 New Feature Flags prototype git-svn-id: https://svn.apache.org/repos/asf/sling/whiteboard/feature-flags@1552202 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 70 ++++++++ .../extensions/featureflags/ExecutionContext.java | 56 ++++++ .../sling/extensions/featureflags/Feature.java | 54 ++++++ .../extensions/featureflags/FeatureProvider.java | 57 ++++++ .../featureflags/impl/ExecutionContextFilter.java | 100 +++++++++++ .../extensions/featureflags/impl/FeatureImpl.java | 196 +++++++++++++++++++++ .../featureflags/impl/ResourceAccessImpl.java | 51 ++++++ .../extensions/featureflags/package-info.java | 30 ++++ 8 files changed, 614 insertions(+) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6a40896 --- /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.4.3-SNAPSHOT</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.resourceaccesssecurity</artifactId> + <version>0.0.1-SNAPSHOT</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.osgi</artifactId> + <version>2.2.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/extensions/featureflags/ExecutionContext.java b/src/main/java/org/apache/sling/extensions/featureflags/ExecutionContext.java new file mode 100644 index 0000000..8e00dfd --- /dev/null +++ b/src/main/java/org/apache/sling/extensions/featureflags/ExecutionContext.java @@ -0,0 +1,56 @@ +/* + * 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.extensions.featureflags; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.ResourceResolver; + +public class ExecutionContext { + + private final ResourceResolver resourceResolver; + + private final SlingHttpServletRequest request; + + public static ExecutionContext fromRequest(final SlingHttpServletRequest request) { + return new ExecutionContext(request); + } + + public static ExecutionContext fromResourceResolver(final ResourceResolver resourceResolver) { + return new ExecutionContext(resourceResolver); + } + + private ExecutionContext(final ResourceResolver resourceResolver) { + this.request = null; + this.resourceResolver = resourceResolver; + } + + + private ExecutionContext(final SlingHttpServletRequest request) { + this.request = request; + this.resourceResolver = request.getResourceResolver(); + } + + public SlingHttpServletRequest getRequest() { + return this.request; + } + + public ResourceResolver getResourceResolver() { + return this.resourceResolver; + } +} diff --git a/src/main/java/org/apache/sling/extensions/featureflags/Feature.java b/src/main/java/org/apache/sling/extensions/featureflags/Feature.java new file mode 100644 index 0000000..ed901c5 --- /dev/null +++ b/src/main/java/org/apache/sling/extensions/featureflags/Feature.java @@ -0,0 +1,54 @@ +/* + * 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.extensions.featureflags; + +import aQute.bnd.annotation.ProviderType; + +/** + * The feature service is the central gateway for feature handling. + * It can be used to query the available features and to + * check whether a feature is enabled for the current execution + * context. + */ +@ProviderType +public interface Feature { + + + /** + * Checks whether the feature is enabled for the given + * execution context. + * + * The actual check is delegated to the {@link FeatureProvider} + * providing the feature. + */ + boolean isEnabled(String featureName, ExecutionContext context); + + /** Get the list of active feature flags */ + String[] getFeatureNames(); + + /** + * Checks whether a feature with the given name is available. + * A feature is available if there is a {@link FeatureProvider} + * for that feature. + * @param featureName + * @return + */ + boolean isAvailable(String featureName); + +} diff --git a/src/main/java/org/apache/sling/extensions/featureflags/FeatureProvider.java b/src/main/java/org/apache/sling/extensions/featureflags/FeatureProvider.java new file mode 100644 index 0000000..66264b9 --- /dev/null +++ b/src/main/java/org/apache/sling/extensions/featureflags/FeatureProvider.java @@ -0,0 +1,57 @@ +/* + * 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.extensions.featureflags; + +import java.util.Map; + +import org.apache.sling.api.resource.Resource; + +import aQute.bnd.annotation.ConsumerType; + +/** + * A feature provider activates one more features. + */ +@ConsumerType +public interface FeatureProvider { + + /** + * Checks whether the feature is enabled for the current execution + * context. + */ + boolean isEnabled(String featureName, ExecutionContext context); + + /** + * Return the list of available features from this provider. + */ + String [] getFeatureNames(); + + /** + * Returns the resource type mapping for a feature. + * This mapping is only used if {@link #isEnabled(String, ExecutionContext)} + * return true for the given feature/context. + */ + Map<String, String> getResourceTypeMapping(String featureName); + + /** + * Checks whether a resource should be hidden for a feature. + * This check is only executed if {@link #isEnabled(String, ExecutionContext)} + * return true for the given feature/context. + */ + boolean hideResource(String featureName, Resource resource); +} diff --git a/src/main/java/org/apache/sling/extensions/featureflags/impl/ExecutionContextFilter.java b/src/main/java/org/apache/sling/extensions/featureflags/impl/ExecutionContextFilter.java new file mode 100644 index 0000000..7ee7b85 --- /dev/null +++ b/src/main/java/org/apache/sling/extensions/featureflags/impl/ExecutionContextFilter.java @@ -0,0 +1,100 @@ +/* + * 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.extensions.featureflags.impl; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +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.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.extensions.featureflags.ExecutionContext; +import org.apache.sling.extensions.featureflags.Feature; + +@Component +@Service(value=Filter.class) +@Property(name="pattern", value="/.*") +public class ExecutionContextFilter implements Filter { + + private static ThreadLocal<ExecutionContextInfo> EXECUTION_CONTEXT; + + @Reference + private Feature feature; + + public static ExecutionContextInfo getCurrentExecutionContextInfo() { + final ThreadLocal<ExecutionContextInfo> local = EXECUTION_CONTEXT; + if ( local != null ) { + return local.get(); + } + return null; + } + + @Override + public void destroy() { + EXECUTION_CONTEXT = null; + } + + @Override + public void doFilter(final ServletRequest req, final ServletResponse res, + final FilterChain chain) + throws IOException, ServletException { + final ThreadLocal<ExecutionContextInfo> local = EXECUTION_CONTEXT; + if ( local != null && req instanceof SlingHttpServletRequest ) { + local.set(new ExecutionContextInfo((SlingHttpServletRequest)req, feature)); + } + try { + chain.doFilter(req, res); + } finally { + if ( local != null && req instanceof SlingHttpServletRequest ) { + local.set(null); + } + } + } + + @Override + public void init(final FilterConfig config) throws ServletException { + EXECUTION_CONTEXT = new ThreadLocal<ExecutionContextInfo>(); + } + + public final class ExecutionContextInfo { + + public final ExecutionContext context; + public final Set<String> enabledFeatures = new HashSet<String>(); + + public ExecutionContextInfo(final SlingHttpServletRequest req, + final Feature feature) { + this.context = ExecutionContext.fromRequest(req); + for(final String name : feature.getFeatureNames()) { + if ( feature.isEnabled(name, context) ) { + enabledFeatures.add(name); + } + } + } + } +} diff --git a/src/main/java/org/apache/sling/extensions/featureflags/impl/FeatureImpl.java b/src/main/java/org/apache/sling/extensions/featureflags/impl/FeatureImpl.java new file mode 100644 index 0000000..65abe86 --- /dev/null +++ b/src/main/java/org/apache/sling/extensions/featureflags/impl/FeatureImpl.java @@ -0,0 +1,196 @@ +/* + * 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.extensions.featureflags.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.felix.scr.annotations.Component; +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.extensions.featureflags.ExecutionContext; +import org.apache.sling.extensions.featureflags.Feature; +import org.apache.sling.extensions.featureflags.FeatureProvider; +import org.osgi.framework.Constants; + +/** + * This service implements the feature handling. + * It keeps track of all {@link FeatureProvider} services. + */ +@Component +@Service(value=Feature.class) +@Reference(name="featureProvider", + cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE, + policy=ReferencePolicy.DYNAMIC, + referenceInterface=FeatureProvider.class) +public class FeatureImpl implements Feature { + + private final Map<String, List<FeatureProviderDescription>> providers = new HashMap<String, List<FeatureProviderDescription>>(); + + private Map<String, FeatureProviderDescription> activeProviders = new HashMap<String, FeatureProviderDescription>(); + + /** + * Bind a new feature provider + */ + protected void bindFeatureProvider(final FeatureProvider provider, final Map<String, Object> props) { + final String[] features = provider.getFeatureNames(); + if ( features != null && features.length > 0 ) { + final FeatureProviderDescription info = new FeatureProviderDescription(provider, props); + synchronized ( this.providers ) { + boolean changed = false; + for(final String n : features) { + if ( n != null ) { + final String name = n.trim(); + if ( name.length() > 0 ) { + List<FeatureProviderDescription> candidates = this.providers.get(name); + if ( candidates == null ) { + candidates = new ArrayList<FeatureProviderDescription>(); + this.providers.put(name, candidates); + } + candidates.add(info); + Collections.sort(candidates); + changed = true; + } + } + } + if ( changed ) { + this.calculateActiveProviders(); + } + } + } + } + + /** + * Unbind a feature provider + */ + protected void unbindFeatureProvider(final FeatureProvider provider, final Map<String, Object> props) { + final String[] features = provider.getFeatureNames(); + if ( features != null && features.length > 0 ) { + final FeatureProviderDescription info = new FeatureProviderDescription(provider, props); + synchronized ( this.providers ) { + boolean changed = false; + for(final String n : features) { + if ( n != null ) { + final String name = n.trim(); + if ( name.length() > 0 ) { + final List<FeatureProviderDescription> candidates = this.providers.get(name); + if ( candidates != null ) { // sanity check + candidates.remove(info); + if ( candidates.size() == 0 ) { + this.providers.remove(name); + changed = true; + } + } + } + } + } + if ( changed ) { + this.calculateActiveProviders(); + } + } + } + } + + private void calculateActiveProviders() { + final Map<String, FeatureProviderDescription> activeMap = new HashMap<String, FeatureImpl.FeatureProviderDescription>(); + for(final Map.Entry<String, List<FeatureProviderDescription>> entry : this.providers.entrySet()) { + activeMap.put(entry.getKey(), entry.getValue().get(0)); + } + this.activeProviders = activeMap; + } + + @Override + public boolean isEnabled(final String featureName, final ExecutionContext context) { + boolean result = false; + final FeatureProviderDescription desc = this.activeProviders.get(featureName); + if ( desc != null ) { + final FeatureProvider prod = desc.getProvider(); + result = prod.isEnabled(featureName, context); + } + return result; + } + + @Override + public String[] getFeatureNames() { + return this.activeProviders.keySet().toArray(new String[this.activeProviders.size()]); + } + + @Override + public boolean isAvailable(final String featureName) { + return this.activeProviders.containsKey(featureName); + } + + + /** + * Internal class caching some provider infos like service id and ranking. + */ + private final static class FeatureProviderDescription implements Comparable<FeatureProviderDescription> { + + public FeatureProvider provider; + public final int ranking; + public final long serviceId; + + public FeatureProviderDescription(final FeatureProvider provider, final Map<String, Object> props) { + this.provider = provider; + final Object sr = props.get(Constants.SERVICE_RANKING); + if ( sr == null || !(sr instanceof Integer)) { + this.ranking = 0; + } else { + this.ranking = (Integer)sr; + } + this.serviceId = (Long)props.get(Constants.SERVICE_ID); + } + + @Override + public int compareTo(final FeatureProviderDescription 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 FeatureProviderDescription ) { + return ((FeatureProviderDescription)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; + } + + public FeatureProvider getProvider() { + return provider; + } + } +} diff --git a/src/main/java/org/apache/sling/extensions/featureflags/impl/ResourceAccessImpl.java b/src/main/java/org/apache/sling/extensions/featureflags/impl/ResourceAccessImpl.java new file mode 100644 index 0000000..a3bed50 --- /dev/null +++ b/src/main/java/org/apache/sling/extensions/featureflags/impl/ResourceAccessImpl.java @@ -0,0 +1,51 @@ +/* + * 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.extensions.featureflags.impl; + +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.extensions.featureflags.Feature; +import org.apache.sling.resourceaccesssecurity.AllowingResourceAccessGate; +import org.apache.sling.resourceaccesssecurity.ResourceAccessGate; + +@Component +@Service(value=ResourceAccessGate.class) +public class ResourceAccessImpl + extends AllowingResourceAccessGate + implements ResourceAccessGate { + + @Reference + private Feature feature; + + @Override + public GateResult canRead(final Resource resource) { + boolean available = true; + final ExecutionContextFilter.ExecutionContextInfo info = ExecutionContextFilter.getCurrentExecutionContextInfo(); + if ( info != null ) { + for(final String name : info.enabledFeatures) { + // we can't check as Feature does not have the api (TODO - we deny for now) + available = false; + break; + } + } + return (available ? GateResult.DONTCARE : GateResult.DENIED); + } +} diff --git a/src/main/java/org/apache/sling/extensions/featureflags/package-info.java b/src/main/java/org/apache/sling/extensions/featureflags/package-info.java new file mode 100644 index 0000000..f1cdd16 --- /dev/null +++ b/src/main/java/org/apache/sling/extensions/featureflags/package-info.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +/** + * Provides a service to interface which may be implemented by applications + * to get notified on cluster topology changes. + * + * @version 1.0 + */ +@Version("1.0") +package org.apache.sling.extensions.featureflags; + +import aQute.bnd.annotation.Version; + -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
